MySQL内置函数实战避坑:从分类框架到索引失效的全面指南
不用再提醒我写一篇关于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,bSUBSTRING_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)决定去哪个库哪张表。有个细节如果除数是0MOD会返回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统计计算中那些说不清的NULLCOUNT、SUM、AVG这类聚合函数是最容易和NULL纠缠不清的。先说COUNT(*)和COUNT(column)的区别COUNT(*)统计的是行数包括NULL行COUNT(column)统计的是该列非NULL值的个数。这俩在逻辑上是天壤之别很多人写COUNT(status)想统计有效记录数结果因为部分行的status是NULL算出来的数字和预期大相径庭还找不出原因。SUM函数对NULL的处理也有讲究。它会把NULL忽略掉但如果所有值都是NULLSUM的结果是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-01LEFT(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(你好)返回6CHAR_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, 游客)但数据库里的空值其实是空字符串而不是NULLIFNULL对空字符串无能为力。修正为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, 默认)空串不是NULLIFNULL管不着日期过滤范围条件DATE_FORMAT(create_time, %Y-%m-%d) ...函数套列导致索引失效字符串长度校验CHAR_LENGTH(str)LENGTH(str)中文按字节计数长度虚高手机号等字符串等值查询WHERE phone 13800138000WHERE 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-$.nameJSON_EXTRACT(json_col, $.name)后者返回值带引号需要JSON_UNQUOTE这张表我建议贴在你经常写SQL的编辑器旁边每次写完SQL对着过一遍。虽然不能覆盖所有情况但绝大多数生产环境的坑都是从这几条里长出来的。4.3 关于内置函数使用的三点个人建议第一点所有内置函数的使用优先考虑能不能让索引列保持独立。这是写高性能SQL的第一原则比背函数参数顺序重要得多。即使你某天忘了某个函数的具体语法只要记住这个原则写出来的SQL至少不会踩性能的大坑。第二点IFNULL加NULLIF这个组合是数据清洗的神器。两个函数单独用都有限制组合起来几乎是空值统一处理的终极解决方案。处理用户输入、导入的外部数据、第三方接口返回的数据时我基本离不开这个组合。第三点尽量少在SQL里做复杂的字符串正则匹配和JSON深查询。MySQL的内置函数虽然强大但毕竟是数据库的能力边界之外的事情。复杂字符串处理交给应用层处理数据库只负责存、取和简单的聚合这样整体架构清晰排查问题也方便。SQL简洁了性能高了读代码的人也轻松了。最后再分享一个小技巧在MySQL客户端里执行HELP 函数名可以快速查询内置函数的官方解释和示例不用每次都开浏览器搜文档。比如HELP CONCAT_WS;直接显示完整的语法说明。这个命令比任何网上搜到的三手教程都权威遇到拿不准的函数先用它查一下能省掉不少查阅时间。内置函数这个东西基础得不能再基础但恰恰是这种基础决定了你写的SQL到底能扛多大流量、躲过多少暗坑。