
做MySQL开发和运维这些年我几乎每天都会碰到这个字段存的是字符串我要当成数字排序日志表里记的是日期文本我要按日期筛选这类需求。在MySQL里解决这种问题最常用的就是convert函数围绕它的字符串转数字、字符串转日期、类型转换函数用法网上资料很散很多帖子还只讲语法不讲坑。这篇我把convert在MySQL里的完整用法、和cast的差异、隐式转换的那些坑、以及真实场景里的排查经验一次性讲透适合写过SQL但没系统研究过类型转换的开发也适合被类型不匹配搞到头大的运维同学。1. 先搞懂CONVERT的语法和能干的事CONVERT在MySQL里其实是双面人它既能做类型转换把一个表达式从一种数据类型转成另一种也能做字符集转换把字符串从一种编码转成另一种。很多时候我们聊convert说的都是第一种但第二种在处理乱码问题时会突然冒出来所以最好一开始就把它的全貌看清。两种语法的写法如下-- 类型转换 CONVERT(expr, type) -- 字符集转换 CONVERT(expr USING transcoding_name)类型转换最常见的几个type取值我按实际使用频率整理了一张表目标类型含义典型用途SIGNED有符号整型123转成数字UNSIGNED无符号整型不含负数的数值转换DECIMAL(M,D)定点小数金额、精度敏感的数字CHAR(N)定长字符串限定长度或格式化DATE日期字符串转日期DATETIME日期时间字符串转日期时间TIME时间时分秒转换BINARY二进制字符串大小写敏感的精确比较1.1 CONVERT的两个形态应用场景不同形态一CONVERT(expr, type)是标量级转换把单个表达式转成目标类型。比如CONVERT(42, SIGNED)结果就是数字42。这个形态最常用字符串转数字、字符串转日期都靠它。形态二CONVERT(expr USING charset_name)是字符集转换比如CONVERT(abc USING utf8mb4)它把字符串从当前字符集转成指定字符集。这个功能我和很多同事聊过大家平时几乎不用但一旦遇到历史库latin1编码导致的中文乱码它就变成救场工具。在后面第4章我会专门讲这个场景。这里要延伸一个容易混淆的点字符串转数字、字符串转日期本质上都是把一种编码形式的数据解析成另一种类型但MySQL对不同type的解析规则差别非常大。数字字符串转SIGNED时MySQL是从左往右连续解析能解析几位算几位。试一下SELECT CONVERT(123abc, SIGNED); -- 123 SELECT CONVERT(abc123, SIGNED); -- 0第一个结果是123因为从第一个字符1开始连续解析数字到a停下。第二个结果是0因为开头就是字母没有可解析的数字。这个行为和CAST完全一致实际上CAST(123 AS SIGNED)和CONVERT(123, SIGNED)就是同一个功能的两种写法。1.2 CONVERT和CAST到底该用哪个经常有人问既然CAST和CONVERT做的事几乎一样MySQL为什么搞两个函数从类型转换功能上看两者完全等价SELECT CAST(123 AS SIGNED) CONVERT(123, SIGNED); -- 1 SELECT CAST(2024-01-15 AS DATE) CONVERT(2024-01-15, DATE); -- 1差别主要在写法来历CAST是SQL标准语法CONVERT是MySQL方言。所以在写正式项目代码时我习惯用CAST标准SQL意味着将来换数据库时迁移成本低。但大家在网上问得最多、讨论得最多的反而是CONVERT因为它写法更简洁而且它多了USING字符集转换的能力CAST没有这个形态。还有一类同学是从SQL Server转过来的需要注意SQL Server里CONVERT的写法是CONVERT(类型, 表达式)比如CONVERT(int, 123)方向和MySQL正好相反MySQL是CONVERT(123, SIGNED)。而且SQL Server的CONVERT带一个样式参数比如CONVERT(varchar, GETDATE(), 112)能把日期格式化MySQL的CONVERT没有这个参数格式化日期需要配合DATE_FORMAT使用。我见过不少人交叉写代码把方向写反然后报表一个红色报错。2. 字符串转数字最频繁的转换需求字符串转数字是我遇到量最大的转换场景。常见的有三种历史表把订单金额、手机号、身份证号当varchar存了现在要参与SUM、AVG聚合接口日志表存了KV字符串value都是文本但要取数字做阈值判断还有varchar的ID要和数字类型的关联键做JOIN。2.1 三种常见写法第一种是显式CONVERTSELECT CONVERT(12.50, DECIMAL(10,2)); -- 12.50 SELECT CONVERT(12.56, SIGNED); -- 13四舍五入取整注意CONVERT(12.56, SIGNED)得到的是13MySQL对带小数部分的字符串转整数时采用的是四舍五入而不是直接截断。如果你想保留小数用SIGNED就会丢失精度要处理金额务必用DECIMAL(M,D)指定精确的小数位。第二种是用算术运算符触发隐式转换SELECT 12.50 0; -- 12.50这是MySQL的隐藏技巧字符串与数字做算术运算时字符串会被隐式转为DOUBLE。优点是写法极简适合临时排查数据缺点是太隐晦团队review时容易误解我不建议进正式代码。如果某天接手老项目看到这种写法把它改成显式CONVERT是更好的维护方向。第三种是用DECIMAL保留精度SELECT CONVERT(12.50, DECIMAL(10,2)); -- 12.50 SELECT CAST(12.50 AS DECIMAL(10,2)); -- 12.50只要涉及金额、单价、评分这类精度敏感字段我强烈建议指定DECIMAL(M,D)M是总位数D是小数位。比如约定金额最长10位、小数2位就写DECIMAL(10,2)。超出精度时MySQL会按四舍五入处理而不是直接报错这点比直接转SIGNED要稳。2.2 字符串ID参与比较和JOIN时的隐藏问题这个场景我踩过很深的坑。假设orders表有一个order_no字段类型是varchar(64)存的都是数字。业务要拿它和payments表关联SELECT o.*, p.pay_amount FROM orders o JOIN payments p ON o.order_no p.order_no;如果payments.order_no是bigintMySQL在比较时会把varchar的order_no隐式转成数字因为数字和字符串比较时字符串会被转成数字。结果就是索引失效全表扫描如果某个order_no含有非数字字符比如123ABC转成数字会解析到123有可能和另一个数字ID 123错误匹配。第二个问题比性能问题更可怕。两个完全不同的单号因为隐式转换撞到一起统计报表就悄悄错了数据还特别难排查。解决办法最彻底的是改表结构让JOIN两侧类型一致。如果一时改不了需要在SQL层面显式处理SELECT o.*, p.pay_amount FROM orders o JOIN payments p ON CAST(p.order_no AS CHAR) o.order_no;这里的关键思路不要对orders表的order_no做转换而是把被驱动表payments的order_no转换成字符串让orders表能正常走索引。如果你反过来写CONVERT(o.order_no, UNSIGNED)orders表索引就废了。这种哪边有索引就保哪边不动的原则是少走冤枉路的核心。3. 字符串转日期格式和性能都要命字符串转日期是另一个高频需求。日志表、接口入参、Excel导入的数据日期经常以各种格式存成字符串。要把字符串转成日期类型CONVERT是首选但它的能力边界必须搞清楚。3.1 标准格式直接转非标准格式用STR_TO_DATEMySQL的CONVERT和CAST在转日期时只认ISO标准格式SELECT CONVERT(2024-01-15, DATE); -- 2024-01-15 SELECT CONVERT(2024-01-15 10:30:00, DATETIME); -- 2024-01-15 10:30:00 SELECT CONVERT(10:30:00, TIME); -- 10:30:00常见可识别的格式包括日期是YYYY-MM-DD日期时间是YYYY-MM-DD HH:MM:SS时间是HH:MM:SS。另外MySQL对一些分隔符比较宽容比如2024/01/15用CONVERT转DATE也能成功因为MySQL的日期解析器能识别/。但这不是可靠契约千万不要依赖这种宽松解析。如果数据是2024年1月15日、15/01/2024、20240115这类非标准写法CONVERT基本无能为力直接返回NULL在严格sql_mode下可能直接报错或警告。这时候必须用STR_TO_DATESELECT STR_TO_DATE(2024年1月15日, %Y年%m月%d日); -- 2024-01-15 SELECT STR_TO_DATE(15/01/2024, %d/%m/%Y); -- 2024-01-15 SELECT STR_TO_DATE(20240115, %Y%m%d); -- 2024-01-15STR_TO_DATE是MySQL处理任意日期格式的唯一正统方案它的格式符和DATE_FORMAT一一对应。最常用的一组格式符含义%Y四位年份%m两位月份%d两位日%H24小时制小时%i分钟%s秒3.2 转日期之后才能真正高效地做日期运算很多人觉得反正varchar也能比较大小为什么非要把日期转成DATE类型这里有个大误区字符串比较是字典序2024-2-1和2024-02-01的字典序结果会出乎意料。而且日期函数在字符串上也不能正确运算。比如要算订单逾期天数SELECT DATEDIFF(CURDATE(), CONVERT(order_date, DATE)) AS overdue_days FROM orders WHERE order_date IS NOT NULL;如果order_date本身就是DATE类型不需要CONVERT如果是varchar标准日期DATEDIFF在处理时也会隐式解析很多时候不转也能算对。但明确做一次CONVERT代码语义更清晰也能让MySQL在WHERE条件里正确处理范围判断SELECT * FROM orders WHERE CONVERT(order_date, DATE) BETWEEN 2024-01-01 AND 2024-01-31;不过要注意性能细节如果order_date是varchar且建有索引你在WHERE左侧写CONVERT(order_date, DATE)索引会失效因为索引里存储的是原始varchar值不是转换后的DATE。正确思路是让右侧的字符串边界匹配列的类型格式或者干脆把列类型改成DATE。这种左侧不动、转换右侧的写法在日期范围查询里同样适用。4. 类型转换的隐性坑索引失效、隐式转换与字符集CONVERT用起来简单但真正让DBA头疼的是它引发的隐式行为和性能问题。这一章讲三个我排查过很多次的坑。4.1 隐式类型转换数据库替你做的决定多半不省心MySQL有一个特性当表达式中出现不同类型的操作数时它会做隐式转换。规则大致如下数字和字符串比较字符串转数字字符串和日期时间比较字符串转日期时间某些情况下是列本身被转换索引自然失效。最经典的例子SELECT * FROM user WHERE phone 13800001111;如果phone是varchar(20)这个查询等价于SELECT * FROM user WHERE CONVERT(phone, SIGNED) 13800001111;于是phone列上的索引失效全表扫描。数据量一旦上了百万一条慢查询就冒出来了。这种问题在慢查询日志里非常典型逻辑一样只差一个引号性能天壤之别。排查手段就是EXPLAIN看type如果从ref变成ALL十有八九是类型没对上。4.2 显式CONVERT列导致索引失效的情况就算你显式写了CONVERT只要写在索引列这一侧同样失效SELECT * FROM orders WHERE CONVERT(order_no, UNSIGNED) BETWEEN 1000 AND 2000;order_no是varchar且建了索引这个查询会全列扫描一遍。MySQL优化器对CONVERT(column, type)这类表达式基本不做反向推导来使用索引。所以类型转换的使用原则要记牢尽量让索引列保持原始类型要转换就把常量侧转过去。例如WHERE order_no CONVERT(1000, CHAR)或者更简单WHERE order_no 1000。如果确实需要对一个列做类型转换后才能过滤且这个查询很频繁建议直接改表结构或者增加生成列ALTER TABLE orders ADD COLUMN order_no_num BIGINT AS (CONVERT(order_no, UNSIGNED)) STORED; CREATE INDEX idx_order_no_num ON orders(order_no_num);生成列Generated Column是这类问题的最优解MySQL 5.7及以上都支持。写入时MySQL自动维护这个列查询时直接走新索引不用每次SQL都做转换。4.3 字符集转换场景下的CONVERTCONVERT(expr USING charset_name)这个写法我在排查乱码时经常用。比如历史遗留表是latin1编码应用层统一utf8mb4读取时SELECT CONVERT(name USING utf8mb4) FROM old_table;把latin1转utf8mb4中文乱码问题通常能解决。注意这里不改变存储在磁盘上的数据只影响本次查询结果。如果你的表本身已经是utf8mb4但连接层的character_set_client和服务器不一致导致乱码应该用SET NAMES解决不需要每次查询都CONVERT那样性能开销很大。字符集转换和类型转换共用CONVERT这个关键字初看很怪但理解成把数据从一种表示形式变成另一种就合理了一个是换编码一个是换类型。这个函数名起得确实是转换二字的集大成者。5. 全套实操案例从日志表中提取统计数据光讲语法过于干巴我完整拆一个真实场景。假设有一个API访问日志表业务方把请求参数全部拼成一个text字段存了现在需要从里面捞统计指标。建表和模拟数据CREATE TABLE api_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, request_time VARCHAR(32), param_text TEXT ); INSERT INTO api_log (request_time, param_text) VALUES (2024/01/15 08:30:12, amount12.50user_id1001), (2024/01/15 09:12:45, amount8.00user_id1002), (2024/01/16 10:01:03, amount100.00user_id1001);现在需求是按天统计每个用户的消费金额。第一步把request_time从2024/01/15 08:30:12转成DATETIME。这个字符串是/分隔MySQL能宽容解析但为了稳定用STR_TO_DATE指定格式SELECT STR_TO_DATE(request_time, %Y/%m/%d %H:%i:%s) AS req_dt FROM api_log;第二步从param_text中提取amount。MySQL里提取子串常用SUBSTRING_INDEXSELECT SUBSTRING_INDEX(SUBSTRING_INDEX(param_text, , 1), , -1) AS amount_str FROM api_log;这里先用切出第一段amount12.50再用切出后面的值12.50。第三步用CONVERT把金额字符串转成DECIMAL并聚合SELECT DATE(STR_TO_DATE(request_time, %Y/%m/%d %H:%i:%s)) AS stat_date, SUBSTRING_INDEX(SUBSTRING_INDEX(param_text, , 2), , -1) AS user_id, SUM(CONVERT( SUBSTRING_INDEX(SUBSTRING_INDEX(param_text, , 1), , -1), DECIMAL(10,2) )) AS total_amount FROM api_log GROUP BY stat_date, user_id;最终统计结果stat_dateuser_idtotal_amount2024-01-15100112.502024-01-1510028.002024-01-161001100.00这里如果把CONVERT去掉直接SUM字符串MySQL也会把字符串隐式转成数字求和看起来能算对。但一旦某个amount是空字符串或者12.50.30这种脏数据麻烦就来了隐式转换会截断解析到12不报错但结果错误而且很难被发现。显式CONVERT其实也不能救脏数据它同样按规则截断真正有效的办法是提前在应用层或者ETL层把数据清洗干净。类型转换不是银弹保证数据源干净才是根本。5.1 常见报错与排查速查表最后把这些年常见的类型转换问题整理成速查表按图索骥能省不少时间现象原因解法CONVERT(abc, SIGNED)返回0而不是报错MySQL宽松解析不能靠异常捕获脏数据提前做数据校验CONVERT(2024-02-30, DATE)返回NULL或警告非法日期先STR_TO_DATE校验或应用层正则拦截WHERE varchar列 数字导致查询极慢隐式转换把列转数字索引失效常量加引号必要时改列类型CONVERT(12.35, DECIMAL(10,1))返回12.4DECIMAL自身精度四舍五入明确D位数否则结果和预期差一分钱中文乱码后用CONVERT(列 USING utf8mb4)解决存储字符集和应用字符集不一致优先SET NAMES/修改表字符集不要习惯性CONVERTSTR_TO_DATE返回NULL就像没转一样格式符与字符串不匹配严格对照%Y%m%d%H%i%s逐一检查5.2 几个值得单独说的经验第一不要在WHERE条件里对索引列做任何转换这是所有数据库的通用原则不只是MySQL。右侧写CONVERT或CAST基本没问题左侧一旦套函数索引大概率废掉。第二处理大批量数据时CONVERT本身有CPU开销。能在外层提前处理的数据就别在每条SQL里重复计算。我曾经优化过一个报表任务去掉某次循环中的多余CONVERT后跑批时间从40分钟降到12分钟。第三MySQL 8.0把隐式转换规则改得更严格了一些从5.7升级后以前能跑的SQL可能现在直接报错或者改走全表。升级前把所有涉及类型的SQL用EXPLAIN扫一遍是省生产事故的最好办法。操作系统级别和数据库版本的不一致往往就藏在这些不起眼的转换行为里。我自己最深的体会是CONVERT不是一个用就完事的函数它背后牵扯的是隐式转换规则、字符集、索引选择、数据精度一整套逻辑。真正的高手不是在SQL里把CONVERT写得飞起而是从一开始就用对类型让数据类型和业务语义严格对应。存储层一个数字不要为了展示方便存成varchar一个日期也不要一百种格式随便存。类型设计对了CONVERT的使用频率自然就降下来了但一旦真需要它前面这些细节就是救命稻草。最后一句话送给被类型转换折磨过的朋友SQL不背锅锅多半在表结构设计上。先从源头把类型规划好再谈怎么写转换函数你的SQL就能少一半脏活累活。