☰
MySQL内置函数实战避坑:从分类框架到索引失效的全面指南
2026/10/11 21:27:42 网站建设 项目流程

不用再提醒我"写一篇关于MySQL内置函数的文章"了——说实话,这标题往这一摆,很多人的第一反应是"内置函数嘛,我天天用",可真要问他一句:SUBSTRING_INDEX和LOCATE到底谁在前谁在后,DATE_FORMAT为什么慢,IFNULL和COALESCE有啥本质区别,十个里有八个会卡壳。我做后端开发这些年,MySQL内置函数是接触最频繁、也最容易被忽视的基础能力,绝大多数线上慢查询、结果集错乱、隐式转换问题,根子都能追到某个内置函数的滥用上。

这篇就把我实际工作中的经验做个彻底梳理:先讲怎么给内置函数建立分类框架,再逐个拆高频函数的使用细节和踩坑点,最后把最隐蔽的、和索引/字符集/类型转换相关的几个大坑单独拎出来说,附上排查速查表。内容偏实战,面向的是日常写SQL的开发者、刚转数据库方向的运维,以及所有想把手头SQL写得更稳的人。

1. 先搭框架:内置函数到底该怎么学才不乱

1.1 为什么死记硬背函数名没有用

MySQL的内置函数加起来有几百个,官方文档翻起来像本字典。但实际项目里90%的查询,用到的函数不超过30个。问题是这30个函数,用法往往藏在细节里——参数顺序反了直接报错或者返回空,类型不匹配导致隐式转换,格式化函数吃掉了索引。死记硬背解决不了这些问题,你得先建立"分类→场景→细节"的认知路径。

我见过太多人写SQL的时候,想到什么函数就Google一下,用完就忘,下次遇到还是Google。这样效率极低,而且很容易被网上的旧版本文章带偏。真正该做的是像整理工具箱一样,把MySQL内置函数按"解决什么问题"分类,每个分类记住几个主力函数,再记住每个主力函数最关键的1到2个易错点,这就足够覆盖日常开发了。

1.2 六大分类:每一类对应一种底层能力

我习惯把MySQL内置函数分成六大类:字符串函数、数值函数、日期时间函数、流程控制函数、聚合函数、JSON函数。这个分类不是按官方文档来的,而是按我写业务SQL时的思考路径来的:

  • 处理用户输入、做数据清洗,走字符串函数;
  • 计算金额、算分页偏移、做数值校验,走数值函数;
  • 统计报表、算时间差、按日期分组,走日期时间函数;
  • 写复杂查询逻辑、动态拼条件,走流程控制函数;
  • 做统计汇总、生成报表数据,走聚合函数;
  • 处理半结构化数据的项目,走JSON函数。

这个框架的价值在于:你拿到一个业务需求,先判断它属于哪类问题,再去对应分类里找函数,而不是面对几百个函数名大海捞针。下面这张总览表就是我日常最常用的主力函数清单,建议收藏。

分类主力函数典型使用场景易错点
字符串CONCAT_WS、SUBSTRING_INDEX、LOCATE、REPLACE、TRIM、REGEXP拼接字段、拆分标签、模糊匹配、清洗空格参数顺序混淆、正则性能差、字符集影响长度
数值ROUND、TRUNCATE、CEILING、FLOOR、MOD金额计算、分页偏移、取整精度模式差异、除零错误
日期时间DATE_FORMAT、STR_TO_DATE、TIMESTAMPDIFF、DATE_ADD、LAST_DAY格式化输出、日期运算、分组统计格式化串走索引失效、时区差异
流程控制IF、IFNULL、CASE WHEN、COALESCE字段映射、默认值填充、逻辑分支IFNULL只判断NULL不判断空串、CASE WHEN编写顺序
聚合COUNT、SUM、MAX/MIN、GROUP_CONCAT统计汇总、字符串拼接除零、NULL参与计算、GROUP_CONCAT长度上限
JSONJSON_EXTRACT、JSON_UNQUOTE、JSON_CONTAINS配置读取、半结构化数据查询JSON路径写错、返回带引号

这个框架建立起来之后,你就不再是"遇到问题查函数",而是"遇到问题先归类,再精准取用"。这个过程本身就是一种能力提升。

2. 高频函数实操拆解:场景、语法与深坑

2.1 字符串函数:最容易栽在参数顺序上

字符串函数是日常用得最多、也是出错率最高的一类。先说最经典的拼接:CONCAT和CONCAT_WS。CONCAT_WS比CONCAT多一个参数,第一个参数是分隔符,后面的参数是待拼接内容,比如要把姓名和手机号用逗号拼起来,CONCAT_WS(',', name, phone)。它比CONCAT强在两点:一是分隔符只需写一次,二是遇到NULL值会自动跳过,不会像CONCAT那样整个结果变成NULL。这一点特别重要,因为业务数据里有NULL太正常了,用CONCAT拼出来全是空。

拆分场景我用得最多的是SUBSTRING_INDEX。它的语法是SUBSTRING_INDEX(str, delim, count),注意顺序:先字符串,再分隔符,最后是取第几个。很多人会记成先分隔符再字符串,一写就反。count是正数表示从左往右数,取第n个分隔符之前的子串;count是负数表示从右往左数,取倒数第n个分隔符之后的子串。比如SUBSTRING_INDEX('a,b,c', ',', 2)返回a,b,SUBSTRING_INDEX('a,b,c', ',', -1)返回c。我在做标签拆分配置的时候,一个字段存逗号分隔的多个值,用这个函数直接拆分,效率比在应用层用代码split再查库高一个量级。

LOCATE和INSTR是找位置的函数,这俩的参数顺序是反的,我也踩过坑。LOCATE(substr, str)是子串在前、原串在后;INSTR(str, substr)是原串在前、子串在后。写完不检查,跑出来结果是0或者位置不对,排查半天才发现顺序反了。这种函数没有对错之分,纯粹是记忆成本高,我的建议是统一只用LOCATE,固定记住"要找什么、去哪里找"这个语序。

字符串清洗场景里,TRIM函数值得单独说。MySQL的TRIM支持三种用法:TRIM(str)去掉首尾空格,TRIM(LEADING 'x' FROM str)去掉开头指定字符,TRIM(TRAILING 'x' FROM str)去掉结尾指定字符。很多人不知道后面两种用法,遇到要去掉固定前缀或后缀的字段时,就用SUBSTRING硬截取,不但要数位数,前缀一变就出错。用TRIM处理,语义明确得多。

正则函数REGEXP是另一个高频坑。它性能差,而且业务语义容易被忽略。WHERE name REGEXP '^张|李'这段正则,看着是匹配"姓张或姓李",但因为|的优先级问题,实际含义变成了"以张开头或者包含李",结果和你预期的完全不一样。正则在MySQL里的优先级和常规正则引擎还有差异,建议复杂正则先在应用层校验逻辑,再用REGEXP执行,不要过分依赖MySQL的正则能力。

2.2 数值函数:精度问题比你想的严重

数值函数看着简单,真正用起来全是细节。ROUND函数就藏着一个大坑:它有两种用法,ROUND(x)和ROUND(x, d),第二个参数d表示保留几位小数。但MySQL的ROUND采用的是四舍五入的"半数离零"规则,在金融场景里经常不对,因为金融计算更常用的是"银行家舍入法"。比如ROUND(2.675, 2)的结果不是2.68,而是2.67,原因在二进制浮点数的精度误差。解决方式有两种:一是用DECIMAL类型存储金额字段,二是在应用层做精确计算,MySQL的浮点数函数只做展示用。

TRUNCATE(x, d)是直接截断,不四舍五入,TRUNCATE(2.675, 2)返回2.67。它和ROUND的区别要记清楚:ROUND是四舍五入,TRUNCATE是截断丢弃。有意思的是,对负数,TRUNCATE(-2.675, 2)返回-2.67,而ROUND(-2.675, 2)返回-2.68,方向不同。向上取整CEILING和向下取整FLOOR没什么坑,只要记住一个向上一个向下就行,但有一个场景容易写错:分页的偏移量计算,总条数除以每页条数后要向上取整得到总页数,写CEILING(total / page_size),经常有人写反成FLOOR,导致最后一页数据永远显示不出来。

MOD函数取余数,看似简单,但要注意它在MySQL里的实现是MOD(N, M),等价于N % M。这在做哈希分表、分库路由的时候非常常用,比如按用户ID取模分到不同的表,MOD(user_id, 10)决定去哪个库哪张表。有个细节:如果除数是0,MOD会返回NULL而不是报错,这在分表路由时特别容易出问题,你没判断NULL就拿着去查库,结果找不到任何数据。

还有个冷门但好用的数函数是DIV,它做整数除法,返回商的整数部分,7 DIV 2返回3。它和FLOOR(7/2)的区别在于,负数场景下DIV是向零取整,FLOOR是向下取整,-7 DIV 2返回-3,而FLOOR(-7/2)返回-4。业务上如果涉及正负数的分页或分组,这个区别要特别留意。

2.3 日期时间函数:格式化的代价你得知道

日期时间函数中,DATE_FORMAT是使用频率最高、也最容易被批评的函数。它能按格式串把日期输出成任意格式,比如DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s')输出标准的"年-月-日 时:分:秒"。问题在于,一旦你对索引字段用DATE_FORMAT做过滤或排序,这个查询就废了——因为优化器无法使用索引来做范围扫描,即使你的created_at字段上有索引,MySQL也只能把每一行都取出来格式化一遍再比较,这就是典型的"函数套列导致索引失效"。

正确的做法是把格式化的动作拆开:如果你要查某一天的数据,应该用范围查询created_at >= '2024-01-01' AND created_at < '2024-01-02',而不是DATE_FORMAT(created_at, '%Y-%m-%d') = '2024-01-01'。前者能走索引,后者必须全表扫描。这个改动带来的性能差异,在大表上动辄是几十倍。

STR_TO_DATE是把字符串转成日期的函数,和DATE_FORMAT互为逆操作。它常用于把外部导入的文本日期转成标准日期类型,比如STR_TO_DATE('2024/01/01', '%Y/%m/%d')。它的一个坑在于格式串和字符串必须严格对应,多一个空格、少一个冒号都会返回NULL,而且不会报错。所以我在做数据清洗的时候,一定先用SELECT STR_TO_DATE('测试串', '对应格式')验证一遍格式,再批量处理。

日期运算里,TIMESTAMPDIFF计算两个日期之间的差值,单位可选SECOND、MINUTE、HOUR、DAY、MONTH、YEAR,非常实用。比如算用户年龄,TIMESTAMPDIFF(YEAR, birth_date, CURDATE())直接拿到周岁。注意它和DATEDIFF的区别:DATEDIFF只算天数差,TIMESTAMPDIFF可以指定任意单位。如果算两个日期相差多少小时,DATEDIFF做不到,得用TIMESTAMPDIFF(HOUR, ...)。

DATE_ADD和DATE_SUB在日期上加或减一个时间间隔,间隔用INTERVAL关键字表示,比如DATE_ADD(CURDATE(), INTERVAL 7 DAY)。这个函数本身没什么坑,但要注意和时间类型一起用的时候,用CURDATE()加出来的结果带不带时分秒会影响比较结果。我遇到过几次明明加了7天,查询条件却匹配不上,最后发现是CURDATE()返回的是DATE类型,比较的时候被隐式转换成了DATETIME,导致边界判断出错。

LAST_DAY返回某月的最后一天,做月度报表时特别好用,LAST_DAY(CURDATE())直接拿到当月最后一天,配合DATE_SUB还能算出上个月的起止时间。这个函数比用DATE_FORMAT加手动拼接月末日期靠谱得多,推荐写月度统计的人用起来。

2.4 流程控制函数:别让NULL悄悄改变结果

IF(expr, v1, v2)和CASE WHEN是流程控制的绝代双骄。IF写法简洁,适合简单的二值判断,比如IF(score >= 60, '及格', '不及格'),但它嵌套多了可读性极差,三层以上的IF嵌套建议一律改成CASE WHEN。CASE WHEN支持多个条件分支,执行顺序是从上到下,第一个满足的条件生效,这个特性既是优势也是隐患——如果你把宽泛的条件写前面,后面的精确条件永远不会触发。

我实际遇到过一个问题:订单状态字段有10个枚举值,要映射成5个业务分组。同事用CASE WHEN status IN (1,2,3) THEN '待付款' WHEN status IN (4,5) THEN '已付款' ...写,逻辑没问题。可后来加了新的状态值11,忘了改映射逻辑,新状态落到默认的ELSE分支,报表数据就开始出现莫名的"其它"。这个坑告诉我们:CASE WHEN的ELSE分支一定要显式写明,哪怕你预期它不会被触发,也要写出来,才能在数据异常时快速暴露问题。

IFNULL(expr1, expr2)的作用是当expr1为NULL时返回expr2。它有个隐蔽的问题:只能判断NULL,不能判断空字符串,但实际业务里"空字符串"和"NULL"经常混着出现。如果你想两者都处理成默认值,得写成IFNULL(NULLIF(name, ''), '未知'),先用NULLIF把空串转成NULL,再交给IFNULL。这个组合写法很容易被忽略,但数据清洗时特别管用。

COALESCE函数类似IFNULL的加强版,它接受多个参数,返回第一个非NULL值,COALESCE(name, phone, '无名')。它的优势是能一次处理多个字段的兜底逻辑,不用层层嵌套IFNULL。另外注意,COALESCE在更新语句里也有妙用:更新时不想覆盖已有值,可以用COALESCE(新值, 旧值)来实现"有值则更新,无值则保留原值"的逻辑,写起来比在应用层拼SQL干净得多。

2.5 聚合函数与GROUP_CONCAT:统计计算中那些说不清的NULL

COUNT、SUM、AVG这类聚合函数是最容易和NULL纠缠不清的。先说COUNT(*)和COUNT(column)的区别:COUNT(*)统计的是行数,包括NULL行;COUNT(column)统计的是该列非NULL值的个数。这俩在逻辑上是天壤之别,很多人写COUNT(status)想统计有效记录数,结果因为部分行的status是NULL,算出来的数字和预期大相径庭,还找不出原因。

SUM函数对NULL的处理也有讲究。它会把NULL忽略掉,但如果所有值都是NULL,SUM的结果是NULL而不是0,这在做报表页面展示时经常炸,页面上显示一个"null"给用户看。我的习惯是IFNULL(SUM(amount), 0),把NULL兜底成0。AVG同理,AVG对NULL是忽略而非置0,所以平均值往往会比直觉偏高。想要把NULL当0参与平均,得写SUM(amount)/COUNT(*)手动算。

GROUP_CONCAT是字符串聚合函数,能把分组内的值拼成一个字符串,比如把某个订单下的所有商品名拼起来:GROUP_CONCAT(product_name SEPARATOR ',')。看起来简单,但有两个隐藏坑。第一,默认最大长度是1024字节,超过这个长度会被静默截断,拼出来的结果不完整。这个参数可以通过SET group_concat_max_len = 10240调整,或者修改数据库配置文件。第二,GROUP_CONCAT默认会按照GROUP BY的顺序拼接,不一定是插入顺序,想控制拼接顺序要用ORDER BY子句:GROUP_CONCAT(product_name ORDER BY create_time SEPARATOR ',')。

聚合函数还有一对容易被混用的兄弟:MAX/MIN用在日期字段上,取到的分别是最大日期和最小日期,这没问题。但用在字符串上时,比较的是字典序,比如MAX(name)取到的是按字母排序最大的字符串,不是字段值最大的记录。这听起来像废话,但真有人在业务里想用MAX(name)去取某个分组里"最新"的一条数据的名字,结果取出来的完全不是那么回事。想要取"最新一条数据",应该用ORDER BY create_time DESC LIMIT 1配合子查询,而不是聚合函数。

2.6 JSON函数:半结构化数据的新战场

从MySQL 5.7开始,JSON类型的支持越来越成熟,JSON函数也成了内置函数里不可忽视的一类。最常用的是JSON_EXTRACT(json_doc, '$.path'),从JSON文档里提取指定路径的值。比如JSON_EXTRACT('{"name":"张三","age":30}', '$.name')返回"张三"——注意,这个结果自带双引号,因为JSON路径提取的返回值保留JSON类型。想拿到纯文本,外面要套一层JSON_UNQUOTE(),这是新手最容易困惑的地方。

更常用的是和->>操作符配合。MySQL提供json_col->>'$.name'的语法,相当于JSON_UNQUOTE(JSON_EXTRACT(json_col, '$.name')),直接返回不带引号的纯文本。写起来简洁多了。从8.0开始还支持JSON_VALUE函数,功能类似,但可以指定返回类型,比如JSON_VALUE(json_col, '$.age' RETURNING UNSIGNED),做类型转换更直接。

JSON_CONTAINS函数用于判断JSON文档里是否包含目标值,比如判断用户的标签数组里有没有"VIP"这个值:JSON_CONTAINS(tags, '"VIP"')。注意,目标值要用JSON格式,字符串必须带引号,写成JSON_CONTAINS(tags, 'VIP')会返回NULL而不是0或1,这个差异很容易让判断逻辑出错。另外JSON_TABLE函数能把JSON数组展开成表结构,配合JOIN做复杂查询,8.0用得多,但语法相对复杂,建议先在小数据量上验证,再放到生产环境。

我自己的经验是,JSON函数适合处理"偶尔要查一下"的半结构化数据,但如果你的业务里高频地按JSON字段做过滤和聚合,那还是老老实实拆成单独列吧。JSON字段走不了常规索引,只能靠VIRTUAL列加索引来优化,维护成本高,得不偿失。

3. 内置函数使用中的性能问题与规范

3.1 函数套列导致索引失效:为什么DATE_FORMAT慢得离谱

前面说过DATE_FORMAT套在索引列上会导致全表扫描,其实这是一大类问题统称:只要在查询条件的列上套了任何函数,索引都会失效。不止日期格式化,LEFT(name, 3) = '张'、YEAR(create_time) = 2024、CONCAT(last_name, first_name) = '张三',这些统统不友好。MySQL对索引使用的判断是:索引列必须保持原始形态参与比较,一旦外面套了函数,优化器就无法利用B+树的叶子节点做范围匹配,只能全表扫。

解决方案不是放弃内置函数,而是把函数逻辑挪到等号的另一边。比如YEAR(create_time) = 2024可以改写成范围条件create_time >= '2024-01-01' AND create_time < '2025-01-01',LEFT(name, 3) = '张'可以改写成name >= '张' AND name < '仉'(这个技巧我实际测试过,字符集是utf8mb4时用下一位字符做范围上界是可行的)。本质思路是:让索引列单独出现在比较表达式的一端,不要参与任何计算转换。

这个优化对报表类的慢查询特别有效。我之前优化过一个按月份统计订单的接口,原SQL用了DATE_FORMAT(create_time, '%Y-%m')做分组和过滤,表里几百万条数据,查询要2秒多。改成范围条件过滤后,同样的结果集,查询时间降到0.2秒以内,十倍的差距,而且完全不需要改任何索引结构。这是内置函数使用规范里最值得记住的一条。

3.2 隐式类型转换:函数结果和字段类型对不上

隐式类型转换是MySQL查询里最隐蔽的坑,它经常和内置函数联动出现。典型场景:字段是VARCHAR,存的是手机号,你写查询条件WHERE phone = 13800138000,手机号是数值类型,MySQL会把VARCHAR字段隐式转换成数值再比较。单看结果发现也能查到数据,但索引已经失效了,因为索引列的类型被改变了。真机测试时数据量小感觉不出来,上生产数据量一上来就慢得没法看。

更隐蔽的场景出现在CASE WHEN或IF的分支里。比如IF(type = 1, quantity, 'N/A'),quantity是整型,'N/A'是字符串,MySQL会把整型值隐式转换为字符串做兼容,在返回结果里全部变成字符串。这倒不太影响正确性,但如果后续对这个结果再做数值计算,就会出问题。类似的还有CONCAT(amount, '元'),明明只是想拼接展示,结果amount被转成字符串,如果后续再参与SUM运算,直接报错或者得到0。

避免这个问题的核心方法是显式转换。需要把字符串转数值就用CAST(x AS SIGNED)或+0,需要把数值转字符串就用CAST(x AS CHAR)。内置函数CAST和CONVERT就是干这个的,CAST(amount AS DECIMAL(10,2))明确指定精度和数据类型,避免MySQL自己猜。我的原则是:所有涉及不同类型拼接或比较的场景,都必须有意识地加CAST,不要让隐式转换在后台悄悄发生。

3.3 字符集和字节数:LENGTH与CHAR_LENGTH的天壤之别

字符串长度相关的内置函数有一个魔鬼细节:LENGTH(str)返回的是字节数,CHAR_LENGTH(str)返回的是字符数。在utf8mb4编码下,一个中文字符占3个字节,一个emoji占4个字节,所以LENGTH('你好')返回6,CHAR_LENGTH('你好')返回2。很多人在做输入长度校验时用了LENGTH,导致中英文混合输入的长度判断总是偏大,存数据库时又超长报错,改了半天才发现是这个区别。

排查技巧很简单:如果字段里可能有中文或emoji,一律用CHAR_LENGTH判断逻辑长度。反过来,如果要计算字段存储占用空间,比如评估一张表能做多少数据量,才需要LENGTH来估算字节占用。两个函数没有优劣,只有适用场景之分。与字符集相关的还有SUBSTRING对位置的定位是基于字符数的而不是字节数,这点和LENGTH正好相反,写截取逻辑时别搞混。

另外字符串比较函数也受字符集影响。MySQL默认的排序规则(collation)决定了字符串相等和排序的判断标准,utf8mb4_general_ci不区分大小写,utf8mb4_bin区分大小写。用UPPER、LOWER做大小写转换,再配合区分大小写的排序规则,才能让比较结果可控。不然你以为查出来的数据是精确匹配,实际上因为collation的原因大小写不同也匹配上了,数据对不上号时无从排查。

3.4 GROUP_CONCAT的长度的坑:默认1024字节

GROUP_CONCAT这个函数我前面提过一句,它的默认长度限制为1024字节,这里细说。当你拼接大量数据时,超出的部分会被MySQL静默截断,返回的结果不完整,而且不报任何错误。我踩过最狠的一次是统计一个分类下的所有商品ID拼接成字符串,作为筛选条件传给下游,因为一个分类下的商品远超1024字节,拼接的结果被截断,导致下游筛选出了部分商品,数据对不上查了两天才定位到是这个长度限制。

解决办法有两种。第一种是会话级调整:SET SESSION group_concat_max_len = 102400;,只对当前连接有效,适合临时调试。第二种是全局调整:在配置文件的[mysqld]部分加group_concat_max_len = 102400,重启后对所有连接生效。实际项目里如果批量生成的字符串确实很长,我建议把长度设成业务的合理上限,比如1MB,既安全又不至于把内存打爆。

还有一个容易忽略的问题:GROUP_CONCAT默认拼出来的字符串不排序,如果你拼接的顺序影响业务逻辑,必须在函数内部加ORDER BY。我遇到过拼接标签时需要按时间倒序,结果每次查出来的标签顺序都不一样,前端展示出来觉得像bug,实际上就是没加ORDER BY导致的。

4. 常见问题排查实录与避坑速查

4.1 三个真实排查案例,每个都是典型

案例一:某统计接口偶发数据少了300条。排查时发现SQL里有WHERE DATE_FORMAT(create_time, '%Y-%m-%d') = '2024-05-20',这个写法导致create_time上的索引失效,全表扫描后过滤。但因为DATE_FORMAT的时间边界问题,5月20日下标的数据被截断成当天零点,导致部分记录被误过滤。改成create_time >= '2024-05-20' AND create_time < '2024-05-21'后,问题消失,查询速度也提升了。这个案例同时踩了索引和格式化边界两个坑。

案例二:用户的昵称为空时,页面上显示为空白而不是默认值。排查发现代码里用的是IFNULL(nickname, '游客'),但数据库里的空值其实是空字符串''而不是NULL,IFNULL对空字符串无能为力。修正为IFNULL(NULLIF(nickname, ''), '游客')后,问题解决。这个案例暴露了业务里"空字符串"和"NULL"混用的典型问题。

案例三:报表里的金额汇总和明细对不上。排查发现SQL里用了SUM(amount),而amount字段允许NULL,当某组所有记录都是NULL时,SUM结果是NULL,往下游传递时被当成0处理,导致汇总对不上。修正为IFNULL(SUM(amount), 0)后正常。这个案例提醒我:聚合并不能把NULL自动当0,得自己兜底。

这三个案例有个共同点:都不是函数不会用,而是对函数的边界行为理解不到位。如果你能把IFNULL不支持空串、SUM对NULL的忽略、DATE_FORMAT的索引代价这几个点都记住,至少能避开80%的内置函数相关的坑。

4.2 内置函数避坑速查表

场景推荐写法避免写法原因
判断字段为空(含空串)IFNULL(NULLIF(name, ''), '默认')IFNULL(name, '默认')空串不是NULL,IFNULL管不着
日期过滤范围条件DATE_FORMAT(create_time, '%Y-%m-%d') = '...'函数套列导致索引失效
字符串长度校验CHAR_LENGTH(str)LENGTH(str)中文按字节计数,长度虚高
手机号等字符串等值查询WHERE phone = '13800138000'WHERE phone = 13800138000隐式类型转换导致索引失效
拼接多个字段CONCAT_WS(',', a, b)CONCAT(a, b)CONCAT遇NULL全返NULL
取"最新一条"排序+LIMIT子查询MAX(create_time)聚合函数不能取整条记录
CASE分支兜底显式ELSE分支不写ELSE未匹配的走默认NULL,数据异常难发现
GROUP_CONCAT拼接超长调大group_concat_max_len直接使用默认值超1024字节被静默截断
金额计算用DECIMAL类型用浮点数+ROUND二进制浮点数精度误差
JSON取字符串值json_col->>'$.name'JSON_EXTRACT(json_col, '$.name')后者返回值带引号,需要JSON_UNQUOTE

这张表我建议贴在你经常写SQL的编辑器旁边,每次写完SQL对着过一遍。虽然不能覆盖所有情况,但绝大多数生产环境的坑,都是从这几条里长出来的。

4.3 关于内置函数使用的三点个人建议

第一点,所有内置函数的使用,优先考虑"能不能让索引列保持独立"。这是写高性能SQL的第一原则,比背函数参数顺序重要得多。即使你某天忘了某个函数的具体语法,只要记住这个原则,写出来的SQL至少不会踩性能的大坑。

第二点,IFNULL加NULLIF这个组合是数据清洗的神器。两个函数单独用都有限制,组合起来几乎是"空值统一处理"的终极解决方案。处理用户输入、导入的外部数据、第三方接口返回的数据时,我基本离不开这个组合。

第三点,尽量少在SQL里做复杂的字符串正则匹配和JSON深查询。MySQL的内置函数虽然强大,但毕竟是数据库的能力边界之外的事情。复杂字符串处理交给应用层处理,数据库只负责存、取和简单的聚合,这样整体架构清晰,排查问题也方便。SQL简洁了,性能高了,读代码的人也轻松了。

最后再分享一个小技巧:在MySQL客户端里执行HELP 函数名可以快速查询内置函数的官方解释和示例,不用每次都开浏览器搜文档。比如HELP CONCAT_WS;直接显示完整的语法说明。这个命令比任何网上搜到的三手教程都权威,遇到拿不准的函数,先用它查一下,能省掉不少查阅时间。内置函数这个东西,基础得不能再基础,但恰恰是这种基础,决定了你写的SQL到底能扛多大流量、躲过多少暗坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询