
MySQL内置函数这块是每个做后端开发的人早晚都要正面刚的东西。不管你是写业务SQL还是做数据分析日期、字符串、数学这几类函数几乎是天天见。很多人平时只会用个NOW()和COUNT()真到要处理复杂业务逻辑的时候就开始在百度上翻来翻去效率极低。我整理这份笔记的初衷很简单把MySQL里最常用、最容易被坑的内置函数一次讲透配合真实的业务场景给出来让你看完就能直接用。这篇文章适合所有跟MySQL打交道的人——刚入门的新手可以把它当速查手册写过几年SQL的老手也能从中找到一些平时没注意到的细节和性能坑。我会按照日期函数、字符串函数、数学函数、其他相关函数四个大块来拆每一块都会附上可复现的SQL示例和踩坑经验。1. 日期函数时间处理是业务开发的地基日期函数用的频率极高几乎所有业务表里都有create_time、update_time这类字段。你写统计报表、做定时任务、按天/月/季度聚合数据全都离不开日期函数。这一章我把最核心的日期函数按用途拆开讲。1.1 最常用的日期获取与格式化函数先看获取当前时间的几个基础函数函数返回值示例结果NOW()当前日期和时间2025-01-15 10:30:45CURDATE()当前日期2025-01-15CURTIME()当前时间10:30:45UTC_DATE()UTC日期2025-01-15UTC_TIME()UTC时间02:30:45这里有个真实业务中常踩的坑NOW()返回的是会话所在时区的当前时间而UTC_DATE()返回的是UTC时间。如果服务器时区设置不对或者客户端连接串里没指定时区你可能会发现“明明库里的时间是对的但Java程序查出来差了8小时”。MySQL 8.0默认时区通常是系统时区强烈建议在连接参数里显式写上serverTimezoneAsia/Shanghai避免因为时区问题导致时间错乱。格式化方面DATE_FORMAT()是出镜率最高的函数。语法是DATE_FORMAT(date, format)format 用占位符控制输出SELECT DATE_FORMAT(NOW(), %Y-%m-%d %H:%i:%s); -- 结果2025-01-15 10:30:45 SELECT DATE_FORMAT(NOW(), %Y年%m月%d日 %H时%i分%s秒); -- 结果2025年01月15日 10时30分45秒常用占位符我列个速查表占位符含义示例%Y四位年份2025%y两位年份25%m两位月份01%c月份1-121%d两位日15%e日1-3115%H24小时制10%h12小时制10%i分钟30%s秒45%W星期名英文Wednesday%w星期数字0周日3反向操作是STR_TO_DATE(str, format)把字符串解析成日期。比如前端传过来一个2025-01-15 10:30:45你想按天分组直接SELECT DATE_FORMAT(STR_TO_DATE(2025-01-15 10:30:45, %Y-%m-%d %H:%i:%s), %Y-%m-%d);STR_TO_DATE在导入CSV、解析外部系统接口数据的时候特别有用但要注意如果字符串和format格式对不上MySQL会返回NULL不会报错。排查数据“莫名丢失”的时候先怀疑这里。1.2 日期计算与区间判断日期计算的核心函数有这几个DATE_ADD()、DATE_SUB()、DATEDIFF()、TIMESTAMPDIFF()、LAST_DAY()。DATE_ADD(date, INTERVAL expr unit)用于给日期加上一个时间间隔DATE_SUB()就是减。INTERVAL后面的单位支持DAY、MONTH、YEAR、HOUR、MINUTE、SECOND等。举个例子-- 三天后的日期 SELECT DATE_ADD(CURDATE(), INTERVAL 3 DAY); -- 结果2025-01-18 -- 两个月前的日期 SELECT DATE_SUB(CURDATE(), INTERVAL 2 MONTH); -- 结果2024-11-15 -- 也可以用负数效果一样 SELECT DATE_ADD(CURDATE(), INTERVAL -3 DAY);实际工作中我更喜欢用负数写法这样不用区分加法还是减法函数统一用DATE_ADD就行逻辑更简洁。注意INTERVAL后面的单位是单数DAY不是DAYS写错了直接报语法错误。两个日期之间相差的天数用DATEDIFF(d1, d2)结果是d1 - d2的天数只看日期部分忽略时间SELECT DATEDIFF(2025-01-20, 2025-01-15); -- 结果5 SELECT DATEDIFF(NOW(), 2025-01-01); -- 如果今天是2025-01-15结果是14要计算精确到小时、分钟、秒的差值就得用TIMESTAMPDIFF(unit, start, end)注意参数顺序是“结束时间在前”还是“开始时间在前”很多人容易搞混。我记的诀窍是返回值 end - start单位由第一个参数指定。SELECT TIMESTAMPDIFF(HOUR, 2025-01-15 08:00:00, 2025-01-15 18:30:00); -- 结果10只计算整数小时不到1小时直接舍去 SELECT TIMESTAMPDIFF(MINUTE, 2025-01-15 08:00:00, 2025-01-15 18:30:00); -- 结果630LAST_DAY(date)返回所在月份的最后一天做月末统计特别方便SELECT LAST_DAY(2025-02-10); -- 结果2025-02-28 -- 求上个月最后一天 SELECT DATE_SUB(LAST_DAY(CURDATE()), INTERVAL 1 MONTH);1.3 日期函数实战统计昨日/当月/近N天数据纸上谈兵没意思直接上几个实际场景。场景一统计昨天新增的用户数很多人的第一反应是写SELECT COUNT(*) FROM user WHERE create_time BETWEEN 2025-01-14 00:00:00 AND 2025-01-14 23:59:59;这样写有两个问题第一你要手动算日期第二如果你漏了23:59:59就会丢掉当天最后一秒的数据。更优雅的写法SELECT COUNT(*) FROM user WHERE DATE(create_time) DATE_SUB(CURDATE(), INTERVAL 1 DAY);但要注意DATE(create_time)包了一层函数会导致索引失效后面章节细说。如果create_time有索引且数据量大建议写成范围查询SELECT COUNT(*) FROM user WHERE create_time DATE_SUB(CURDATE(), INTERVAL 1 DAY) AND create_time CURDATE();这个写法不仅可以用到索引而且逻辑上覆盖了“昨天00:00:00到昨天23:59:59.999”的所有数据堪称最稳写法。场景二统计本月每天的订单量用GROUP BY配合DATE_FORMATSELECT DATE_FORMAT(order_time, %Y-%m-%d) AS day, COUNT(*) AS order_count FROM orders WHERE order_time DATE_FORMAT(CURDATE(), %Y-%m-01) AND order_time DATE_ADD(DATE_FORMAT(CURDATE(), %Y-%m-01), INTERVAL 1 MONTH) GROUP BY DATE_FORMAT(order_time, %Y-%m-%d);DATE_FORMAT(CURDATE(), %Y-%m-01)是当月第一天DATE_ADD(..., INTERVAL 1 MONTH)是下个月第一天用左闭右开区间[当月第一天, 下月第一天)过滤可以捞到整个月的所有数据。2. 字符串函数文本处理的十八般武艺字符串函数在数据清洗、脱敏、拼接、格式转换里天天用到。MySQL的字符串函数比较多但核心的就那几个玩熟了能解决绝大多数问题。2.1 拼接、截取与替换拼接用CONCAT(str1, str2, ...)SELECT CONCAT(MySQL, , 函数); -- 结果MySQL 函数这里有一个新手必踩的坑CONCAT 只要有一个参数是 NULL整个结果就是 NULL。很多人拼用户地址的时候某个字段为空结果整条地址都没了。解决办法是用CONCAT_WS或者IFNULL。CONCAT_WS(separator, str1, str2, ...)是带分隔符的拼接而且它会跳过NULL值不会跳过空字符串SELECT CONCAT_WS(-, 2025, 01, 15); -- 结果2025-01-15 SELECT CONCAT_WS(-, 2025, NULL, 15); -- 结果2025-15NULL被跳过截取子串用SUBSTRING(str, pos, len)或者SUBSTR(str, pos, len)两者等价SELECT SUBSTRING(Hello MySQL, 7, 5); -- 结果MySQL SELECT SUBSTRING(Hello MySQL, -5, 5); -- 结果MySQL负数表示从右边数第几个开始截字符串替换用REPLACE(str, from_str, to_str)SELECT REPLACE(www.mysql.com, mysql, oracle); -- 结果www.oracle.com注意REPLACE是替换所有出现的子串不是只替换第一个。如果需要只替换第一个得自己写函数或者用正则一般业务中很少用到。2.2 大小写转换、去空格与填充大小写转换没什么可说的UPPER()和LOWER()SELECT UPPER(mysql), LOWER(MySQL); -- 结果MYSQL 和 mysql去空格有三个函数这里要特别讲清楚函数作用TRIM(str)去掉字符串首尾的空格LTRIM(str)去掉开头的空格RTRIM(str)去掉结尾的空格注意TRIM默认只去空格不去\t制表符和换行符。数据导入时经常出现“看着没空格但查不到”的情况大多是隐藏的换行符在捣鬼可以用REPLACE把\r\n清掉SELECT REPLACE(REPLACE(column_name, \r, ), \n, ) FROM table_name;填充函数LPAD(str, len, padstr)和RPAD(str, len, padstr)常在生成流水号、订单号时用SELECT LPAD(42, 5, 0); -- 结果00042 SELECT RPAD(A1, 4, *); -- 结果A1**LPAD的实际场景很多比如把自增ID拼成固定长度的编号CONCAT(ORD, LPAD(id, 6, 0))生成ORD000001这样的订单号。2.3 字符串位置与判断LOCATE/INSTR/FIND_IN_SET查找子串位置最常用的是LOCATE(substr, str)和INSTR(str, substr)两者略有差别LOCATE可以附加第三个参数指定起始位置INSTR不行。返回的是第一次出现的位置从1开始找不到返回0。SELECT LOCATE(SQL, MySQL SQL); -- 结果3 SELECT INSTR(MySQL SQL, SQL); -- 结果3 SELECT LOCATE(SQL, MySQL SQL, 4); -- 结果7从第4个字符开始找FIND_IN_SET(str, strlist)这个函数很多人首次看到会懵。它是判断str是否存在于逗号分隔的字符串列表中常用于处理“标签”字段比如用户表里有个 tags 字段存1,3,5想查包含标签3的用户SELECT FIND_IN_SET(3, 1,3,5); -- 结果2返回位置从1开始 SELECT FIND_IN_SET(2, 1,3,5); -- 结果0注意strlist里的逗号后面不要加空格否则匹配不到。这个函数很实用但它的性能不怎么样因为它无法走索引只能在数据量小的维度表上用或者配合全文索引使用否则还是老老实实做关联表吧。2.4 字符串函数实战手机号脱敏、订单号生成脱敏是高频需求。前端展示用户列表时手机号不能全显要变成138****1234SELECT CONCAT(LEFT(phone, 3), ****, RIGHT(phone, 4)) AS masked_phone FROM user;这个写法简洁但要注意phone字段必须是字符串类型如果是数字要用CAST(phone AS CHAR)转一下否则LEFT和RIGHT会先把数字转成字符串再处理倒也不会报错但建议显式转换逻辑更清晰。订单号生成也是字符串函数的典型应用。自增ID太短、太容易被遍历通常需要补零加前缀SELECT CONCAT(ORD, DATE_FORMAT(NOW(), %Y%m%d), LPAD(id, 6, 0)) FROM orders;假设id是42输出就是ORD20250115000042。日期部分带上之后光看订单号就知道下单日期后续做分库分表按订单号取模也方便。3. 数学函数数值运算与统计利器数学函数不复杂但用不对会产生很隐蔽的错误。这里我把常用函数按功能分组重点讲易错点。3.1 取整与随机ROUND/CEIL/FLOOR/RAND四个取整函数差别必须记牢函数行为示例ROUND(x)四舍五入到整数ROUND(2.5) 3ROUND(x, d)四舍五入保留d位小数ROUND(2.567, 2) 2.57CEIL(x)/CEILING(x)向上取整进一法CEIL(2.1) 3FLOOR(x)向下取整去尾法FLOOR(2.9) 2一个我实测过的细节MySQL的ROUND()对0.5的处理是“远离零”而ROUND(2.5)返回3。但在某些编程语言或Excel里ROUND(2.5)可能返回2银行家舍入。如果你们的业务对精度要求极高比如财务金额计算建议在应用层进行舍入别完全依赖MySQL。RAND()返回[0,1)的随机浮点数无参数每次调用结果都不同SELECT RAND(); -- 0.123456789... SELECT RAND(100); -- 固定种子结果固定。排序取出随机N条经典写法SELECT * FROM article ORDER BY RAND() LIMIT 5;这个写法在数据量大时性能极差因为ORDER BY RAND()要对所有行生成随机值再排序全表扫描十几万行都能卡半天。如果只是要随机一条可以先SELECT COUNT(*)然后用随机offset或者使用ORDER BY RAND()配合LIMIT但限定表数据量小或者只查一列时再用。更好的方案是SELECT * FROM article WHERE id (SELECT FLOOR(RAND() * (SELECT MAX(id) FROM article))) ORDER BY id LIMIT 5;——但注意如果id有空洞删除过数据结果可能少于5条。生产环境我一般建议用应用层取随机ID的策略数据库只负责按ID取数据。3.2 绝对值、符号与余数ABS/SIGN/MOD这三个函数简单但实用场景不少。ABS(x)绝对值SELECT ABS(-10); -- 结果10SIGN(x)返回负数、0、正数的符号分别对应 -1、0、1SELECT SIGN(-9), SIGN(0), SIGN(9); -- 结果-1 0 1MOD(x, y)取余数等价于x % ySELECT MOD(10, 3); -- 结果1MOD常用于分库分表、按ID分桶、奇偶判断。比如把用户ID为奇数的分到A库偶数的分到B库就可以在SQL里写WHERE MOD(user_id, 2) 0来筛选。这里提一个信号MySQL里MOD()的结果符号跟随被除数也就是MOD(-10, 3)结果是 -1 而不是1。如果你在业务中期望取余结果永远是正数比如哈希环形分桶记得先取绝对值再取余。3.3 幂、开方与对数POWER/SQRT/EXP/LOGPOWER(x, y)或者POW(x, y)是 x 的 y 次方SQRT(x)是开平方SELECT POWER(2, 10); -- 结果1024 SELECT SQRT(16); -- 结果4EXP(x)是自然常数 e 的 x 次方LOG(x)是自然对数LOG10(x)是常用对数。这几个在数据分析和算法计算中偶尔用到业务SQL中不多见但做机器学习特征工程时有时候会用LOG压缩数据范围比如价格取对数后再做回归。3.4 数学函数实战价格核算、分页随机、百分比计算场景一金额四舍五入订单金额往往需要保留两位小数但数据库里存的是DECIMAL(10,2)如果中间算出了超过两位的小数就要用ROUND修正SELECT ROUND(price * quantity * discount, 2) AS actual_paid FROM order_detail;注意DECIMAL字段精度定义如果DECIMAL(10,2)MySQL 在运算时有可能自动转换成DECIMAL高精度再计算不用担心精度丢失但如果你用的是FLOAT或DOUBLE浮点误差就开始了金额字段请务必用DECIMAL这是血泪教训。场景二随机分页随机推荐场景可以先计算总数再随机偏移SET total (SELECT COUNT(*) FROM product); SET offset FLOOR(RAND() * total); SELECT * FROM product ORDER BY id LIMIT offset, 1;用变量方式可以避免ORDER BY RAND()全排序但OFFSET过大时性能依然不理想。如果产品表有自增ID且基本连续最推荐用SELECT * FROM product WHERE id FLOOR(RAND() * (SELECT MAX(id) FROM product)) ORDER BY id LIMIT 1;。场景三计算百分比统计订单状态占比结果保留两位小数并拼接百分号SELECT status, CONCAT(ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 2), %) AS percent FROM orders GROUP BY status;这里用了窗口函数SUM(COUNT(*)) OVER()求总数是MySQL 8.0的特性。如果还跑在5.7可以用子查询SELECT status, CONCAT(ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM orders), 2), %) AS percent FROM orders GROUP BY status;注意COUNT(*) * 100.0里那个.0目的是把整数计数转成浮点避免整数除法导致结果为0。这个坑特别隐蔽整数百分比算出来全是0排查半天才发现没有转浮点。4. 其他相关函数流程控制、聚合与加密除了三大类常用函数MySQL还有一批“其他函数”在日常写SQL时同样不可或缺。我把它们归为流程控制、聚合、加密和信息类来聊。4.1 流程控制函数IF、IFNULL、NULLIF、CASE WHENIF(expr, if_true, if_false)是写SQL时非常顺手的三目运算符SELECT name, IF(age 18, 成年, 未成年) AS age_group FROM user;IFNULL(expr1, expr2)专门处理NULL替换只要第一个参数不为NULL就返回它否则返回第二个SELECT IFNULL(nickname, 未设置昵称) FROM user;NULLIF(expr1, expr2)和IFNULL相反如果两个参数相等返回NULL不相等返回第一个参数。它常用在防止除零错误SELECT total_amount / NULLIF(quantity, 0) AS avg_price FROM orders;如果quantity为0NULLIF返回NULL整个除法结果为NULL而不会报“division by 0”错误。你可以在应用层再判断结果是否为NULL。但是要注意这个只是“延缓”了报错如果avg_price在WHERE里参与比较NULL会直接被过滤掉别以为是bug。CASE WHEN是功能最强大的条件表达式写多条件判断时比多个IF嵌套清晰得多SELECT id, CASE WHEN score 90 THEN 优秀 WHEN score 60 THEN 及格 ELSE 不及格 END AS grade FROM exam_result;注意CASE的每个分支返回类型最好一致比如都是字符串。否则MySQL会隐式转换可能带来意想不到的结果。CASE还有简单的写法CASE expr WHEN value THEN ... END但它只能做等值判断实际中我常用第一种搜索式。4.2 聚合函数与分组统计COUNT/SUM/AVG/MAX/MIN、GROUP_CONCAT聚合函数都熟但COUNT有好几个版本要注意表达式行为COUNT(*)统计行数包含NULLCOUNT(1)统计行数包含NULL效果等同COUNT(*)COUNT(column)统计该列“非NULL”的行数COUNT(DISTINCT column)统计该列不同的非NULL值数量统计用户数但某个用户nickname是NULL你写COUNT(nickname)就会少算。除非确实想统计非空昵称数否则一律用COUNT(*)。COUNT(DISTINCT col)常用于UV统计SELECT COUNT(DISTINCT user_id) AS uv FROM access_log;注意DISTINCT在多列上使用是COUNT(DISTINCT col1, col2)表示两列组合的去重不是分别去重后相加这个容易混淆。GROUP_CONCAT是一个特别加分的函数它能把分组内的多行数据拼接成一个字符串。比如查一个班级所有学生的姓名SELECT class_id, GROUP_CONCAT(student_name ORDER BY student_id SEPARATOR ,) AS students FROM class_student GROUP BY class_id;默认分隔符是逗号默认长度限制是group_concat_max_len参数默认1024字节。拼接内容很长时会静默截断造成数据缺失。如果遇到GROUP_CONCAT结果长度不对记得先查一下这个变量必要时调大SET SESSION group_concat_max_len 10240;GROUP_CONCAT配合DISTINCT可以给拼接结果去重GROUP_CONCAT(DISTINCT status)。聚合函数最常用的场景是分组统计但很多人会犯一个语义错误在SELECT里同时出现聚合函数和非聚合列且非聚合列不在GROUP BY中。MySQL 5.7默认只开启了ONLY_FULL_GROUP_BY的提示模式8.0默认开启严格模式会直接报错。解决方法是把非聚合列加到GROUP BY或者用ANY_VALUE()包一下-- 错误示例8.0会报错 SELECT name, COUNT(*) FROM user GROUP BY age; -- 正确示例 SELECT name, COUNT(*) FROM user GROUP BY name, age;4.3 加密与信息函数MD5/SHA1/PASSWORD、DATABASE/USER/VERSION加密函数里最常用的是MD5(str)和SHA1(str)它们返回固定长度的十六进制哈希字符串SELECT MD5(123456); -- 结果e10adc3949ba59abbe56e057f20f883e SELECT SHA1(123456); -- 结果7c4a8d09ca3762af61e59520943dc26494f8941bMD5通常用于生成文件的指纹、校验数据一致性、做签名校验。这里必须提醒不要用MD5存密码。MD5已经被大量彩虹表覆盖跑字典极快。即便加盐也建议用SHA2()配合高强度盐或者干脆用bcrypt等专用密码哈希算法在应用层完成。MySQL也提供了PASSWORD(str)函数但这个函数是给MySQL用户账号系统用的5.7开始官方就标记废弃了8.0里已经被移除。如果你在8.0里调用PASSWORD()会直接报错。所以业务代码里别碰它老老实实MD5或SHA2密码存储更推荐应用层做。信息函数平时用得不频繁但调试时很有用SELECT DATABASE(); -- 当前数据库名 SELECT USER(); -- 当前用户如 rootlocalhost SELECT VERSION(); -- MySQL版本号如 8.0.36 SELECT CONNECTION_ID(); -- 当前连接ID用于排查线程写通用工具类SQL时可以用VERSION()判断版本做兼容逻辑。DATABASE()在动态SQL拼接横表转竖表、生成列名时会用到。4.4 其他函数实战状态判断、数据脱敏、兼容性处理场景一订单状态的中文展示订单表存status枚举数字0待支付、1已支付、2已发货、3已完成、4已取消展示时需要翻译SELECT id, CASE status WHEN 0 THEN 待支付 WHEN 1 THEN 已支付 WHEN 2 THEN 已发货 WHEN 3 THEN 已完成 WHEN 4 THEN 已取消 ELSE 未知状态 END AS status_text FROM orders;场景二cookie/敏感数据脱敏对用户邮箱做部分遮盖SELECT CONCAT(LEFT(email, 3), ***, SUBSTRING(email, LOCATE(, email))) AS masked_email FROM user;这个写法先把之前的字符拿3个然后拼***再拼从开始的域名部分。比直接在应用层处理要效率高一次查询直接输出前端可展示的字段。场景三兼容MySQL 5.7和8.0有些函数是8.0新增的比如REGEXP_REPLACE()、REGEXP_LIKE()、RANK()等窗口函数。如果项目需要兼容5.7千万别在SQL里直接写这些函数。遇到需要正则替换的场景5.7里只能用多层REPLACE()嵌套模拟或者把数据拉到应用层处理。上线前建议统一SELECT VERSION()做一次环境嗅探避免函数解析失败。5. 函数使用避坑指南与性能建议前面讲函数时穿插了不少坑这一章我集中整理最常见的坑和性能优化思路都是实战中容易踩雷的地方。5.1 常见错误与NULL陷阱NULL运算传染任何算术运算、字符串拼接里出现NULL结果都是NULL。SELECT 1 NULL; -- 结果NULL SELECT CONCAT(a, NULL); -- 结果NULL解决办法要么用IFNULL转换要么用COALESCE(col, 0)。COALESCE支持多个参数返回第一个非NULL比IFNULL更灵活。字符串与数字隐式转换abc和数字比较时MySQL会把字符串转成数字转换不了就变成0从而导致逻辑错误。比如SELECT * FROM user WHERE age abc实际查询的是age 0匹配到一堆年龄为0或NULLNULL不等于0所以不会命中的记录。建议每次变量传入前检查类型。日期比较别用字符串WHERE create_time 2025-01-15这个是合法的但不会命中2025-01-15 10:30:45这条记录因为日期时间不等于日期。要么用DATE(create_time)包一层会牺牲索引要么写成create_time 2025-01-15 AND create_time 2025-01-16后者索引有效强烈推荐。5.2 函数与索引失效问题这个坑太重要单独说。MySQL B树索引是按列原始值排序的。如果你在索引列上套一个函数WHERE DATE(create_time) 2025-01-01MySQL无法利用create_time索引因为索引里存的是完整日期时间而你在比较的时候用的是函数处理后的值不是原始值的范围。优化方式是把函数挪到等号另一边或者改写成范围条件-- 反例DATE(create_time) 包住了索引列索引失效 SELECT * FROM orders WHERE DATE(create_time) 2025-01-15; -- 正例等值范围索引可用 SELECT * FROM orders WHERE create_time 2025-01-15 00:00:00 AND create_time 2025-01-16;同理LEFT(name, 1) 张也会导致索引失效应该写成name LIKE 张%这个能走索引前缀。经验法则你写的WHERE条件里如果索引列被函数包裹、参与了算术运算id 1 100或者类型转换索引大概率失效。优化方向是改变条件写法而不是牺牲索引去迁就函数。5.3 版本兼容性哪些函数是8.0新增或废弃的MySQL 5.7到8.0函数层面有几个值得注意的变化函数5.78.0备注PASSWORD()可用提示废弃移除报错不能再用于业务GROUP_CONCAT()可用可用行为基本一致RAND()可用可用性能问题依旧REGEXP_REPLACE()不可用可用正则替换神器REGEXP_LIKE()不可用可用正则判断窗口函数RANK()等不可用可用需要8.0如果项目要从5.7升级到8.0先全局搜索一下PASSWORD(和REGEXP相关的代码。另外MySQL 8.0默认字符集是utf8mb4和5.7默认utf8有差异可能会影响字符串函数的排序和长度计算升级后要重新检查涉及LENGTH()、CHAR_LENGTH()的SQL。5.4 函数调用的性能思维避免在SELECT里对大量行使用昂贵的函数。比如ORDER BY RAND()这个前面讲过数据量大时全表排序会拖垮性能。再比如对每行执行MD5()做校验能用批处理的尽量在应用层批量计算。另一个性能点是尽量把函数计算下推到应用层还是上推到数据库我的经验是简单的加减乘除、字符串截取、日期格式化数据库算没问题。复杂的加密哈希、JSON解析能提前算好存字段就别在SQL里算。如果实在需要在SQL里用REGEXP_REPLACE、JSON_EXTRACT对大数据集做处理先限缩数据量再加并行查询别一股脑全表扫描。遇到SQL卡慢先EXPLAIN看看有没有Using filesort、Using temporary再结合索引失效的排查点逐项检查。函数类性能问题多半是索引失效引起的所以最核心的还是那句话不要在索引列上做函数运算。结尾一点个人体会内置函数我用了这么多年最深的感触是函数本身不难难的是“组合”和“边界”。日期函数要配合业务日历理解字符串函数要时刻提防NULL和隐式转换数学函数要清楚浮点误差和取整规则……每一个背后都藏着实际业务里会遇到的坑。平时我写SQL有个习惯凡是拿不准的函数先SELECT单测一遍把各种边界输入都试一下再放到正式查询里。尤其是NULL参与运算的场景十个逻辑错误里至少有五个都是从NULL来的。另一点小技巧是把高频使用的函数片段沉淀成项目笔记。比如脱敏SQL、日期区间SQL、分组拼接SQL这些代码套路几乎每个项目都会复用。用的时候复制过来改个表名就行既减少出错概率也省得每次重新查文档。MySQL内置函数不算多静下心来花半天时间把所有函数过一遍未来写SQL的效率和信心都会明显提升。希望这篇笔记能帮你省掉一些不必要的踩坑时间。