news 2026/10/11 3:07:06

MySQL内置函数实战指南:从字符串到窗口函数避开性能坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL内置函数实战指南:从字符串到窗口函数避开性能坑

干我们这行的,写SQL就像写字一样,MySQL内置函数就是最常用的那套笔画。别小看这几十个函数,用得好,原来要写十几行业务逻辑的查询,一行就能解决;用得不好,线上慢查询一抓一大把,报表算错数还得老板亲自来问。这篇文章我把平时实战中高频使用、坑也踩得最多的内置函数整套拆开讲一遍,配合具体场景和写法,你能直接用上。

1. 先搞清楚函数分类与选型思路

MySQL内置函数看着多,实际分类并不复杂,核心就六类:字符串函数、数值函数、日期时间函数、聚合函数、流程控制函数,以及近几个版本越来越常用的JSON函数。窗口函数虽然是分析场景的利器,但语法上相对独立,我放到后面单独讲。

这里先说一个很关键的思路:能用内置函数解决的事,尽量不要用应用层Java/Python代码去算。原因是数据库函数直接跑在存储引擎之上,避免了数据传输和应用服务器内存占用,尤其在WHERE、ORDER BY、GROUP BY这些关键子句里内置函数处理得当还能走索引(需要注意写法,后面细说)。反之,如果你把数据捞出来在代码里逐行算,数据量一上来性能和资源消耗都会很难看。

怎么快速找到对应函数?我一般遵循三个原则:

  1. 先明确你要操作的对象类型:是字符串、数字、日期,还是字段间关系判断,直接去对应分类里找。
  2. 再确认想要的输出:是要截取、拼接、转换、聚合还是生成布尔值判断,这一步能帮你在同类型函数里快速缩小范围。
  3. 最后考虑可读性和兼容性:同样的效果,能用标准SQL写的就不要用方言特性,比如字符串拼接用CONCAT就比用||更稳妥(虽然MySQL也支持||,但还有SQL_MODE的坑)。

2. 字符串函数:最常用的拼图工具

字符串函数是日常写SQL用到频率最高的一类,没有之一。尤其是做数据清洗和报表字段加工的时候,几乎天天打交道。

2.1 拼接与截取:CONCAT、SUBSTRING、LEFT/RIGHT

-- 拼接:用户姓名+手机号脱敏 SELECT CONCAT(last_name, first_name, ' ', LEFT(phone, 3), '****', RIGHT(phone, 4)) FROM user_info; -- 截取:取订单号从第5位开始的6个字符 SELECT SUBSTRING(order_no, 5, 6) AS short_no FROM order_table;

CONCAT有个比较容易踩的坑:只要任何一个参数为NULL,整个结果就是NULL。这在做报表拼接时非常坑。比如地址字段,省份为空,市区正常,CONCAT之后整条地址都没了。实际开发中我推荐用CONCAT_WS,第一个参数是分隔符,后面参数即使有NULL也会跳过:

SELECT CONCAT_WS(' ', province, city, district, detail_address) FROM user_address;

SUBSTRING有三个常被记混的写法。SUBSTRING(str, pos)是从第pos位截到末尾;SUBSTRING(str, pos, len)是从第pos位开始截len个长度;还有SUBSTRING(str FROM pos FOR len)这种SQL标准写法。要注意MySQL的索引位置是从1开始数的,不是从0开始,别和Java里的substring混了。

2.2 查找与替换:LOCATE、REPLACE、TRIM

LOCATE(substr, str)返回子串第一次出现的位置,常用于判断是否包含某个关键字,配合IF或CASE来用。注意别和INSTR搞混,INSTR(str, substr)参数顺序正好相反。

-- 找出所有商品名里包含“限量”的订单 SELECT order_id, product_name FROM order_detail WHERE LOCATE('限量', product_name) > 0;

REPLACE做字符串替换是最直观的。但是有一个细节:REPLACE是全局替换,不是只替换第一个匹配项。如果你只想去掉字符串中间的一个特定空格,手一快就全部替换了。遇到这种需求,建议先用LOCATE定位,再用SUBSTRING手工拼一下。

TRIM函数不只是去首尾空格,还能指定去除字符:

-- 去掉字符串两端的竖线分隔符 SELECT TRIM(BOTH '|' FROM '|sku_001|sku_002|'); -- 常见组合:清洗数据时把换行符和制表符也一起去掉 SELECT TRIM(REPLACE(REPLACE(column_name, '\r', ''), '\n', ''));

2.3 长度与格式转换:CHAR_LENGTH、LENGTH、LPAD/RPAD

CHAR_LENGTH和LENGTH的差别值得重视。CHAR_LENGTH按字符数计算,一个中文算1;LENGTH按字节计算,在UTF-8下中文一个字符算3字节。比如你要开发一个姓名长度校验,必须用CHAR_LENGTH,否则老外的名字和中文名字统计口径完全乱了。

LPAD/RPAD在生成定长编码、流水号时特别好用。比如订单号需要统一8位:

-- 把自增ID补成8位,左边补0 SELECT LPAD(order_id, 8, '0') AS order_no_padded FROM orders;

还有一个比较少人关注但很实用的函数:FIELD(value, val1, val2, ...),它返回value在参数列表中的位置。灵活用可以解决自定义排序的需求,比如你想让状态按“待付款→已付款→已发货→已完成”的顺序展示,而不是默认的字母序:

SELECT order_id, status FROM orders WHERE status IN ('pending', 'paid', 'shipped', 'completed') ORDER BY FIELD(status, 'pending', 'paid', 'shipped', 'completed');

2.4 字符串函数的常用注意点汇总

场景推荐函数避坑要点
拼接多字段CONCAT_WS避免某字段NULL导致整体为NULL
截取中间内容SUBSTRING起始位置从1开始
判断包含关系LOCATE参数顺序是子串在前
定长流水号LPAD长度超过定义时左补失效
清洗首尾字符TRIM支持BOTH/LEADING/TRAILING三种方位
统计字符个数CHAR_LENGTH区分字符与字节

3. 数值函数与精度陷阱

数值函数看似简单,但金融、统计类业务对精度要求极高,一个ROUND用错可能对不上账。

3.1 四舍五入:ROUND、TRUNCATE

ROUND是按指定位数四舍五入,TRUNCATE则直接截断不四舍五入。这两个区别一定要刻在脑子里:

SELECT ROUND(123.456, 2); -- 123.46 SELECT TRUNCATE(123.456, 2); -- 123.45 SELECT ROUND(123.456, 0); -- 123 SELECT ROUND(123.456, -1); -- 120,负数表示小数点左边

ROUND还有一个隐藏行为:第二参数不写的时候,默认四舍五入到整数。但如果你写ROUND(2.5)和ROUND(3.5),结果可能和你学数学时的预期不一样——MySQL的ROUND采用“四舍六入五成双”的银行家舍入法的一半机会,2.5会变成2,3.5会变成4。具体看浮点数的二进制表示,所以金融计算里不要依赖ROUND,而是用DECIMAL类型配合应用层算法,或者在SQL里先扩大100倍用整数运算。

3.2 取整:FLOOR、CEIL、CEILING

FLOOR向下取整,CEIL向上取整。分页场景算总页数就是经典用法:

SELECT CEIL(COUNT(*) / 20) AS total_pages FROM orders;

负数场景容易混淆:FLOOR(-1.2)结果是-2,CEIL(-1.2)结果是-1。如果你需要的是向零取整,即直接把小数部分去掉,那是TRUNCATE(x, 0),别搞混了。

3.3 绝对值、符号与幂运算

ABS、SIGN、POW/POWER、SQRT、MOD这些基础函数不复杂,但MOD有两个地方要留心:

  1. MOD对负数结果也是负数,比如MOD(-7, 3)返回值是-1,不是2。做取模分表时,建议先对绝对值取模再乘符号,避免出现负数分表键。
  2. 取模还可以用来判断奇偶、周期性任务,比如每天跑批时只处理偶数天的数据,WHERE MOD(day_of_month, 2) = 0。

随机数RAND()是个有个性的函数,不传参数每次调用返回0到1之间的浮点数,传入固定种子后序列就固定了。测试环境需要稳定抽样时,用RAND(100)这种固定种子的写法,能让每次结果一致,方便复现问题。

-- 随机抽样固定种子,测试环境可复现 SELECT * FROM user_info ORDER BY RAND(20240501) LIMIT 10;

3.4 数值与字符串互相转换

CAST和CONVERT都支持类型转换。常规写法:

SELECT CAST('123.45' AS DECIMAL(10,2)); SELECT CONVERT('2024-01-15', DATE);

需要注意:字符串转数值时,MySQL的隐式转换可能会造成索引失效。比如字段是varchar类型,你WHERE num_field = 123,MySQL会自动把字符串和数字比较时转成数值,一旦字段上有索引就很可能没法正常走。实践中应该养成左边字段不动、右边参数主动CAST成对应类型的习惯。

4. 日期时间函数:业务报表的基石

日期时间函数是数据分析类需求的基础。很多看起来不复杂的需求,比如统计昨日、本月、上季度,拆开用对函数后实现差异很大。

4.1 获取当前时间:NOW、CURDATE、CURTIME、SYSDATE

NOW()和CURDATE()最常用。NOW()返回当前日期时间,CURDATE()只返回日期,CURTIME()只返回时间。这里要特别注意NOW()和SYSDATE()的差异:NOW()在一条SQL语句执行时取一次值,整条语句内恒定;SYSDATE()是函数实际执行时获取当前时间。这在长查询、存储过程循环里会导致时间不一致。

-- 潜藏的BUG:同一SQL里两次调用SYSDATE()结果可能差几秒 -- 建议业务统一用NOW() SELECT SYSDATE(), SLEEP(2), SYSDATE();

4.2 日期格式化与解析:DATE_FORMAT、STR_TO_DATE

DATE_FORMAT是最常用的展示层函数。格式串里**%Y是四位年份,%y是两位年份;%m是两位月份,%c是不带前导零的月份;%d是两位日,%e是不带前导零的日;%H是24小时制,%h是12小时制**。这些细节拼错一个就全表数据错乱。

SELECT DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:%s') AS formatted_time FROM orders; -- 统计某小时段的订单数 SELECT DATE_FORMAT(create_time, '%Y-%m-%d %H') AS hour_bucket, COUNT(*) FROM orders GROUP BY hour_bucket;

STR_TO_DATE是DATE_FORMAT的逆操作,把字符串按指定格式解析成日期。ETL场景经常需要处理各种来源的日志时间戳:

SELECT STR_TO_DATE('2024/06/15 22:30:00', '%Y/%m/%d %H:%i:%s');

4.3 日期加减:DATE_ADD、DATE_SUB、INTERVAL

DATE_ADD和DATE_SUB配合INTERVAL关键字,可以做日、周、月、季度、年的加减:

-- 三天前的订单 SELECT * FROM orders WHERE create_time >= DATE_SUB(NOW(), INTERVAL 3 DAY); -- 下个季度的起始日 SELECT DATE_ADD('2024-04-01', INTERVAL 1 QUARTER);

性能建议:WHERE子句里不要让函数套在字段上。比如WHERE DATE(create_time) = CURDATE(),会让索引失效。正确写法是:

-- 推荐写法:字段裸露,函数作用在右值 SELECT * FROM orders WHERE create_time >= CURDATE() AND create_time < DATE_ADD(CURDATE(), INTERVAL 1 DAY);

4.4 提取日期组成部分:YEAR、MONTH、DAY、WEEK、QUARTER

这些函数在报表分组里很常用:

SELECT YEAR(create_time) AS year_no, MONTH(create_time) AS month_no, DAY(create_time) AS day_no, QUARTER(create_time) AS quarter_no, WEEK(create_time) AS week_no FROM orders;

WEEK函数有个模式参数需要注意:WEEK(date, 0)表示以周日为一周的第一天,WEEK(date, 1)表示以周一为一周的第一天。国内业务习惯按周一作为一周开始,所以直接写WEEK(create_time)得到的周数可能和业务口径对不上,建议明确写WEEK(create_time, 1)。

4.5 日期差值:DATEDIFF、TIMESTAMPDIFF

DATEDIFF只按天算差值,精确到天:

SELECT DATEDIFF('2024-06-20', '2024-06-15'); -- 5

TIMESTAMPDIFF更灵活,可以指定单位(SECOND、MINUTE、HOUR、DAY、WEEK、MONTH、QUARTER、YEAR):

-- 用户注册时长(按月) SELECT user_id, TIMESTAMPDIFF(MONTH, register_time, NOW()) AS registered_months FROM user_info;

TIMESTAMPDIFF在算年龄时有个小技巧:用YEAR单位计算年龄,结果自动忽略未满整年的部分,比手动DATEDIFF再除以365要准确,因为它不会受闰年影响。

5. 聚合函数与分组统计的进阶玩法

聚合函数是报表类SQL的灵魂。除了常用的COUNT、SUM、AVG、MAX、MIN,这里我想重点聊几个实战中容易忽略的细节。

5.1 COUNT的三种写法和语义差异

COUNT()统计所有行数;COUNT(1)和COUNT()性能基本相同;COUNT(column_name)只统计该列非NULL的行数。这个差异在可空字段上体现得最明显:

SELECT COUNT(*) AS total_orders, COUNT(pay_time) AS paid_orders, -- 未支付订单pay_time是NULL,不会计入 COUNT(DISTINCT user_id) AS unique_users FROM orders;

COUNT(DISTINCT column)在数据量大时性能损耗明显。如果需求只是判断某个值是否存在,优先用EXISTS,不要COUNT然后判断>0。

5.2 SUM的空值陷阱

SUM函数对NULL值不会报错,但它会把NULL直接忽略,而不是当作0。如果整组都是NULL,SUM返回NULL,不是0。报表展示时你期望的是0,结果页面上显示一个空字符串,前端还得特判。建议组合COALESCE:

SELECT COALESCE(SUM(amount), 0) AS total_amount FROM orders WHERE status = 'pending';

5.3 GROUP_CONCAT的妙用

GROUP_CONCAT可以把分组内的某个字段拼接成一个字符串,适合做一对多关系的汇总展平。比如一个订单对应多个商品标签,需要把标签变成一行:

SELECT order_id, GROUP_CONCAT(tag_name ORDER BY sort_no SEPARATOR ',') AS tag_list FROM order_tags GROUP BY order_id;

GROUP_CONCAT有两个限制很坑:默认最大长度是1024字节,拼接超长会被静默截断;第二个是排序和去重语法比较好记:在字段里写ORDER BY或者DISTINCT。如果业务场景需要更长结果,可以在会话级别或者配置里调大group_concat_max_len。

5.4 HAVING与WHERE的分工

WHERE在分组前过滤原始行,HAVING在分组后过滤聚合结果。这个属于基础中的基础,但实际开发中常见的问题是:有人为了省事,把原本可以在WHERE里过滤的条件写进了HAVING。假如数据量几百万,这个写法会让分组和聚合白白处理一批不应该进入的数据,慢查询往往就是这么来的。

-- 正确写法:先WHERE过滤掉“已取消”订单再做聚合 SELECT user_id, SUM(amount) AS total_spent FROM orders WHERE status != 'cancelled' GROUP BY user_id HAVING SUM(amount) > 1000;

6. 流程控制函数:把判断逻辑写进SQL

很多在代码里写的if-else逻辑,其实可以直接用MySQL的流程控制函数在SQL层完成。合理使用能大量减少应用层数据搬运,让统计逻辑更集中。

6.1 IF函数与IFNULL的适用边界

IF(expr, val1, val2)是最基础的三元判断。适合简单二选一:

SELECT product_name, IF(stock_count > 0, '有货', '缺货') AS stock_status FROM products;

IFNULL(a, b)是IF的简化版,专门处理NULL值,逻辑等价于IF(a IS NOT NULL, a, b)。注意IFNULL只能判断一个参数,如果是空字符串''它不是NULL,IFNULL不会回退。

6.2 CASE WHEN:真正的多分支判断

CASE WHEN比IF灵活得多,支持多条件、范围判断:

SELECT order_id, CASE WHEN amount >= 5000 THEN '大额订单' WHEN amount >= 1000 THEN '中等订单' WHEN amount > 0 THEN '小额订单' ELSE '异常订单' END AS order_level FROM orders;

用法上建议留意的是:CASE WHEN按顺序短路匹配。所以条件的先后顺序要仔细安排,把最具体的条件放在前面,否则会出现某个订单落入第一个匹配分支后就结束了。另一种写法CASE column WHEN value THEN ...是等值匹配写法,不支持范围判断,看情况选即可。

6.3 NULLIF与COALESCE的妙用

NULLIF(a, b):当a等于b时返回NULL,否则返回a。常见场景是避免除以零:

SELECT total_amount / NULLIF(total_count, 0) AS avg_amount FROM stats;

当total_count为0时,NULLIF把它变成NULL,整个除法的结果就是NULL,避免了报错,比应用层加if判断更简洁。

COALESCE是查找列表中第一个非NULL值:

SELECT COALESCE(real_name, nick_name, '匿名用户') AS display_name FROM user_info;

这里有一个嵌套使用的进阶技巧:先NULLIF把不希望当成合法值的值转成NULL,再COALESCE取备用值。

-- 电话号码为空字符串时展示手机号,手机号也空展示座机 SELECT COALESCE(NULLIF(phone, ''), mobile, landline) AS contact FROM customer;

7. JSON函数:MySQL也能当文档数据库用

MySQL 5.7之后JSON支持越来越完善,8.0版本里JSON函数已经足够应对大部分轻量文档存储场景。如果你们业务有“变长属性”的存储需求,JSON字段可以省掉一堆扩展表。

7.1 查询JSON:JSON_EXTRACT与->、->>运算符

JSON_EXTRACT(json_doc, path)提取JSON中指定路径的值:

-- 假设extra_info存的是{"address":{"city":"广州","district":"天河"},"level":3} SELECT JSON_EXTRACT(extra_info, '$.address.city') AS city, JSON_EXTRACT(extra_info, '$.level') AS level FROM users;

->和->>是简写:->返回带引号的JSON值,->>返回纯字符串。实际使用中,对比等值条件时你要用->>,因为->取出来的值还带着双引号,和字符串比较会失败。这个坑我遇到不止一次。

-- 错误示例(大概率匹配不上) SELECT * FROM users WHERE extra_info->'$.level' = '3'; -- 正确示例 SELECT * FROM users WHERE extra_info->>'$.level' = '3';

7.2 生成JSON:JSON_OBJECT与JSON_ARRAY

做接口返回需要组装JSON时,不用在应用层拼字符串,SQL里直接生成:

SELECT user_id, JSON_OBJECT( 'name', nick_name, 'tags', JSON_ARRAY('vip', '老用户') ) AS user_json FROM users;

7.3 JSON聚合:JSON_ARRAYAGG与JSON_OBJECTAGG

这两个是聚合函数,在报表里把分组内的行聚合成JSON数组或对象:

SELECT category_id, JSON_ARRAYAGG(product_name) AS product_list FROM products GROUP BY category_id;

JSON_OBJECTAGG(key, value)可以把分组内的键值对聚合成一个JSON对象,比如成绩单:

SELECT student_id, JSON_OBJECTAGG(course_name, score) AS score_map FROM score_table GROUP BY student_id;

7.4 JSON修改与删除

JSON_SET、JSON_INSERT、JSON_REMOVE这几个函数在日常维护场景里会用到。JSON_SET是更新或插入指定路径,JSON_INSERT是只插入更新不存在路径:

-- 更新或新增key UPDATE users SET extra_info = JSON_SET(extra_info, '$.level', 5) WHERE user_id = 1001; -- 删除某个key UPDATE users SET extra_info = JSON_REMOVE(extra_info, '$.old_key') WHERE user_id = 1001;

用JSON字段时我强烈建议:JSON里的key名不要用中文,虽然MySQL支持,但后续所有JSON_EXTRACT路径都会变长,可读性直线下降,排查问题很痛苦。

8. 窗口函数与分析查询

窗口函数在MySQL 8.0里终于补齐了。以前要“分组内排名”、“分组内TopN”这类需求,得用临时变量或者自连接,写法又绕又慢。现在有ROW_NUMBER、RANK、DENSE_RANK、SUM OVER等函数,清晰得多。

8.1 分组排序:ROW_NUMBER与RANK/DENSE_RANK的区别

ROW_NUMBER()为分组内每行分配唯一的连续序号;RANK()会跳过排名,如并列第一之后直接到第3名;DENSE_RANK()不跳排名,并列第一之后还是第2名。看这个对比:

SELECT student_name, subject_name, score, ROW_NUMBER() OVER (PARTITION BY subject_name ORDER BY score DESC) AS row_no, RANK() OVER (PARTITION BY subject_name ORDER BY score DESC) AS rank_no, DENSE_RANK() OVER (PARTITION BY subject_name ORDER BY score DESC) AS dense_no FROM scores;

如果成绩相同,RANK和DENSE_RANK并列,ROW_NUMBER必须分出先后,顺序由ORDER BY决定,是不确定的。如果你要求并列成绩必须同排名,用RANK;如果要求每个学生都有一个唯一排名号码,用ROW_NUMBER。

8.2 分组TopN:窗口函数+子查询

找出每个分类下销量最高的商品:

SELECT category_id, product_name, sales_cnt FROM ( SELECT category_id, product_name, sales_cnt, ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY sales_cnt DESC) AS rn FROM product_sales ) t WHERE rn = 1;

这条SQL是面试高频题,也是实际业务里“排行榜”“筛选每条分组最新记录”的通用写法。

8.3 移动计算:SUM/AVG OVER

移动总和、移动平均值在趋势分析里常用:

SELECT create_date, daily_amount, SUM(daily_amount) OVER (ORDER BY create_date) AS cumulative_amount, AVG(daily_amount) OVER (ORDER BY create_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_7d FROM daily_stats;

第一行SUM OVER是累计总和。第二行AVG OVER用ROWS BETWEEN指定窗口范围,算的是“最近7天平均”。窗口计算的结果可以让后续SQL语句直接使用,减少应用层循环,效率提升非常明显。

8.4 窗口函数使用禁忌

窗口函数不能直接用在WHERE子句里,必须包一层子查询或CTE。CTE(WITH子句)在涉及多个窗口计算的复杂查询里,可读性提升明显:

WITH ranked AS ( SELECT department_id, employee_name, salary, RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rk FROM employees ) SELECT * FROM ranked WHERE rk <= 3;

另外窗口函数虽然强大,但数据集在内存/临时盘空间有限时会落地到磁盘,超大结果集下性能不一定好。报表场景可以放心用,OLTP高并发查询里尽量别把窗口函数甩到业务主路上。

9. 常见问题排查与性能实战

最后这部分,我系统梳理一下内置函数实际使用中容易踩的坑和排查方向,都是平时支持一线开发时反复遇到的问题。

9.1 函数导致索引失效

这是最普遍的性能杀手。核心原则一句话:左边字段保持原样,右边参数随便套函数。

-- 反例:DATE函数包住字段,索引失效 SELECT * FROM orders WHERE DATE(create_time) = '2024-06-15'; -- 正例:字段裸露,右侧用区间 SELECT * FROM orders WHERE create_time >= '2024-06-15 00:00:00' AND create_time < '2024-06-16 00:00:00';

字符串字段和数值比较:varchar字段存的全是数字时,MySQL会自找麻烦做隐式转换,让索引失效。排查慢查询时,EXPLAIN看到type=ALL且rows巨大,先看一眼WHERE条件两边的类型是否一致。

9.2 隐式字符集转换

两张表联表查询,关联字段一个utf8mb4和一个utf8,或排序规则不一致(utf8mb4_general_ci和utf8mb4_unicode_ci),MySQL可能对字段做隐式转换,索引失效还是小事,严重时结果乱套。排查方法:SHOW CREATE TABLE看字符集,或EXPLAIN看Extra里有没有Using where+Using index之外的异常提示。

9.3 聚合函数加WHERE和HAVING混用结果不直观

比如统计“近30天下单用户中,累计消费超过1万元”的名单,有人先GROUP BY再加HAVING SUM(amount) > 10000,结果把最近30天之外的消费也统计进去了。正确做法是先用WHERE限定窗口,再聚合,再HAVING。

9.4 数值精度丢失

报表金额对不上账,先看有没有直接拿FLOAT/DOUBLE字段做累加或ROUND。MySQL的FLOAT在累加高精度金额时可能有二进制浮点误差。金额字段建议使用DECIMAL(10,2)或DECIMAL(12,2)。已经用了FLOAT的存量表,至少要在聚合函数外面套ROUND控制精度,但根本解法还是改字段类型。

9.5 处理NULL不统一

NULL在整个函数体系里的行为非常不统一,一定要主动用IFNULL、COALESCE、NULLIF去规范。业务上明确“空=0”的数值字段,建议在表设计时就用NOT NULL DEFAULT 0,省一堆后面的麻烦。

9.6 常用函数排查速查表

现象可能原因解决方向
CONCAT结果空白某个字段为NULL改用CONCAT_WS或包IFNULL
日期分组对不上时区未统一检查jdbc连接serverTimezone与session_time_zone
周统计数字不对WEEK默认周日开始显式指定WEEK(date, 1)
COUNT比明细少字段有NULL值按业务需求选择COUNT(*)或COUNT(column)
SUM结果为空该组全部为NULLCOALESCE(SUM(...), 0)
字符串数字比较不走索引隐式类型转换统一字段类型或CAST右侧条件
GROUP_CONCAT被截断默认长度1024调大group_concat_max_len
JSON比较条件匹配不上用了->而非->>等值判断用->>取纯文本

10. 几个实战经验总结

最后分享一条我个人体会很深的经验:内置函数虽然概念不难,但每次上线之前,建议把SQL里涉及的每个函数都在测试库里跑一遍边界值。比如字符串为''、字段为NULL、数值取到-1、日期正好是闰年2月29,这些边界情况往往才是生产事故的高发区。

还有一个习惯值得养成:函数套函数嵌套超过三层的时候,就该考虑拆开写,用子查询先加工一层,再有外部查询继续加工。可读性好,排查问题也方便。我以前接过一个历史报表,字段里套了6层函数,调了一晚上才理清逻辑。后来宁可多写一层CTE,也不做这种无人能维护的“函数套娃”。

再补一个小技巧:像DATE_FORMAT这种格式化函数,在GROUP BY里可以直接按格式化后的字符串分组,但如果只按年月分组,其实用YEAR(create_time), MONTH(create_time)两层分组效率更高,对索引更友好,因为格式化会强制转字符串。具体取舍还要看表的索引结构和数据量。

MySQL内置函数的边界远不止我上述列举的,比如加密函数MD5/SHA2、空间函数、全文检索函数MATCH AGAINST。真正常用的就集中在上面这几类。把常用的一百多个函数吃透,边用边总结自己的场景,效果远比背函数手册好。希望这篇能帮你少踩几个坑,遇到报表问题能更快定位方向。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 3:03:36

Qt三方界面库共享实战:qmake与CMake配置及避坑指南

简介&#xff1a;这份资源是面向Qt开发者的第三方界面库LQFramKit源码共享包&#xff0c;适合希望提升GUI开发效率、减少重复造轮子的中初级程序员。库中对图标资源管理、弹出框调用、引导界面设计及进度条、日历选择器等常用控件做了统一封装&#xff0c;并附带示例项目与API文…

作者头像 李华
网站建设 2026/10/11 3:03:33

鸿蒙hdc工具包详解:环境配置、常用命令与避坑指南

简介&#xff1a;这是一套面向鸿蒙应用开发者的设备调试与终端交互工具集合&#xff0c;定位类似安卓平台上的调试桥工具&#xff0c;核心价值在于让开发者能够通过命令行方式连接鸿蒙终端、传输指令并获取设备反馈。工具包内含三十个独立文件&#xff0c;压缩后体积约为十四兆…

作者头像 李华
网站建设 2026/10/11 3:02:58

电磁泄漏防护全解析:从屏蔽室建设到红黑分离的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/11 3:02:46

基于 SpringBoot 的校园二手交易系统:从需求分析到完整实现

每年六月前后&#xff0c;毕业生宿舍楼下总是堆满带不走的课本、台灯、小风扇和收纳箱。这些东西对毕业生来说已经成了负担&#xff0c;对刚入学的学弟学妹来说却是实打实的刚需。可惜传统的校园交易方式长期停留在QQ群刷屏和线下摆摊&#xff0c;信息过载、图片失效、找不到历…

作者头像 李华
网站建设 2026/10/11 2:59:58

C# WinForms Chart时间轴毫秒级缩放方案

简介&#xff1a;本资源是一份面向C#初中级开发者的时间序列图表开发实践包&#xff0c;聚焦Chart控件中以DateTime为X轴并实现交互式缩放的核心难点&#xff0c;适用于数据可视化、工业监控、日志分析等需动态展示时序趋势的Windows Forms应用场景。压缩包共187个文件&#xf…

作者头像 李华
网站建设 2026/10/11 2:59:30

基于PLM的数字化工厂:打通BOM与变更闭环的落地指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华