MySQL内置函数是数据库系统中预定义的一系列功能模块,它们可以直接在SQL语句中调用,用于数据处理、计算和转换。这些函数就像是数据库工具箱里的各种工具,能帮我们高效完成特定操作而无需自己编写复杂逻辑。
在实际开发中,我经常看到很多开发者重复造轮子,用应用程序代码实现本可以用内置函数轻松完成的操作。合理使用这些函数不仅能减少代码量,还能显著提升查询效率——因为函数运算发生在数据库引擎内部,避免了数据往返传输的开销。
字符串函数是日常开发中使用频率最高的一类。这里重点介绍几个实战中特别有用的:
CONCAT():我经常用它拼接多字段内容。比如用户表的姓和名分开存储时:
sql复制SELECT CONCAT(last_name, ' ', first_name) AS full_name FROM users;
注意NULL值会污染整个结果,可以用CONCAT_WS()避免:
sql复制SELECT CONCAT_WS(' ', last_name, first_name) FROM users;
SUBSTRING()的坑点:第二个参数是起始位置(从1开始),第三个是长度。我见过不少人误以为第二个参数是结束位置:
sql复制-- 正确用法:从第2个字符开始截取3个字符
SELECT SUBSTRING('MySQL', 2, 3); -- 结果'ySQ'
REGEXP操作符虽不是函数但非常强大。最近我用它快速验证了一批邮箱格式:
sql复制SELECT email FROM users WHERE email REGEXP '^[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}$';
处理财务数据时这些函数尤为重要:
ROUND()的银行家舍入:MySQL默认使用四舍五入,但要注意它遵循IEEE 754标准:
sql复制SELECT ROUND(2.5); -- 3
SELECT ROUND(-2.5); -- -3
如果需要银行家舍入(四舍六入五成双),需要自己实现逻辑。
随机数生成有个性能陷阱:
sql复制-- 低效写法(全表扫描)
SELECT * FROM products ORDER BY RAND() LIMIT 1;
-- 高效写法(适用于大表)
SELECT * FROM products WHERE id >= (SELECT FLOOR(MAX(id) * RAND()) FROM products) LIMIT 1;
处理时间数据时这些函数能省去大量应用层代码:
DATE_FORMAT()的格式字符串容易记混:
sql复制-- 把datetime转成'2023年07月20日'格式
SELECT DATE_FORMAT(NOW(), '%Y年%m月%d日');
特别注意:%m是数字月份(01-12),%M是英文月份名称(January-December)
计算年龄的精准方法:
sql复制SELECT TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) -
(RIGHT(CURDATE(), 5) < RIGHT(birth_date, 5)) AS age
FROM employees;
这个写法考虑了生日是否已过,比简单年份相减更准确。
MySQL 8.0引入的窗口函数彻底改变了复杂查询的写法。几个典型场景:
计算移动平均(金融分析常用):
sql复制SELECT date, price,
AVG(price) OVER (ORDER BY date ROWS 6 PRECEDING) AS 7_day_avg
FROM stock_prices;
解决"每组取前N条"经典问题:
sql复制SELECT * FROM (
SELECT product_id, category, price,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) AS rn
FROM products
) ranked WHERE rn <= 3;
处理半结构化数据时JSON函数族非常实用:
从JSON数组提取元素:
sql复制SELECT JSON_EXTRACT('{"tags":["mysql","database"]}', '$.tags[0]');
-- 结果: "mysql"
更简洁的->操作符:
sql复制SELECT column->'$.key' FROM table;
动态更新JSON字段的某个属性:
sql复制UPDATE products
SET specs = JSON_SET(specs, '$.weight', 10.5)
WHERE id = 123;
在索引列上使用函数会导致索引失效:
sql复制-- 无法使用name上的索引
SELECT * FROM users WHERE UPPER(name) = 'JOHN';
-- 解决方案1:使用函数索引
ALTER TABLE users ADD INDEX ((UPPER(name)));
-- 解决方案2:存储计算列
ALTER TABLE users ADD name_upper VARCHAR(255) AS (UPPER(name));
ALTER TABLE users ADD INDEX (name_upper);
在存储过程中过度使用函数可能导致性能问题:
sql复制DELIMITER //
CREATE PROCEDURE update_sales()
BEGIN
-- 低效:在循环内重复调用NOW()
DECLARE cur_time DATETIME;
SET cur_time = NOW(); -- 先获取一次
WHILE condition DO
INSERT INTO sales_log(time) VALUES (cur_time);
-- 而不是直接使用NOW()
END WHILE;
END //
DELIMITER ;
当内置函数不满足需求时,可以创建UDF(用户定义函数):
c复制#include <mysql.h>
#include <string.h>
extern "C" {
my_bool levenshtein_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
longlong levenshtein(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error);
}
my_bool levenshtein_init(UDF_INIT *initid, UDF_ARGS *args, char *message) {
if (args->arg_count != 2) {
strcpy(message, "需要2个字符串参数");
return 1;
}
return 0;
}
longlong levenshtein(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error) {
// 实现编辑距离算法
}
bash复制g++ -shared -o levenshtein.so -I /usr/include/mysql levenshtein.cpp
sql复制CREATE FUNCTION levenshtein RETURNS INTEGER SONAME 'levenshtein.so';
不同MySQL版本函数支持有差异:
| 函数/特性 | 5.6 | 5.7 | 8.0 |
|---|---|---|---|
| 窗口函数 | ❌ | ❌ | ✅ |
| JSON支持 | ❌ | ✅ | ✅ |
| 公用表表达式(CTE) | ❌ | ❌ | ✅ |
| 函数索引 | ❌ | ✅ | ✅ |
升级MySQL版本时要特别注意函数行为变化,比如:
结合多种函数完成复杂报表:
sql复制SELECT
DATE_FORMAT(order_date, '%Y-%m') AS month,
COUNT(*) AS total_orders,
SUM(amount) AS revenue,
SUM(amount) / COUNT(DISTINCT user_id) AS avg_per_user,
RANK() OVER (ORDER BY SUM(amount) DESC) AS rank_by_revenue,
JSON_OBJECT('min', MIN(amount), 'max', MAX(amount)) AS amount_stats
FROM orders
WHERE YEAR(order_date) = 2023
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
HAVING COUNT(*) > 100
ORDER BY month;
这个查询展示了: