MySQL 函数大全与性能边界
MySQL 函数可以分成两大类:
- 内置函数:MySQL 自带的数值、字符串、日期、聚合、窗口、JSON、正则、系统等函数。
- 存储函数:使用
create function创建、保存在数据库中的自定义函数。
真正需要掌握的不是函数名称有多少,而是四件事:函数输入输出、NULL 与类型转换、时区和字符集语义、函数放在查询条件中对索引和执行计划的影响。
本文以 MySQL 8.x 为主。部分函数、参数和函数索引能力在 5.7 与不同 8.x 小版本中存在差异,使用前应通过 select version() 和目标环境验证。
函数知识地图
flowchart TD
A["MySQL 函数"] --> B["单行函数"]
A --> C["多行函数"]
A --> D["自定义函数"]
B --> B1["数值 / 字符串 / 日期时间"]
B --> B2["条件 / NULL / 类型转换"]
B --> B3["JSON / 正则 / 系统 / 加密"]
C --> C1["聚合函数"]
C --> C2["窗口函数"]
D --> D1["存储函数"]| 类型 | 输入与输出 | 示例 |
|---|---|---|
| 标量函数 | 每个输入行通常得到一个结果 | lower(name)、abs(amount) |
| 聚合函数 | 多个输入行归并为一行或每组一行 | count(*)、sum(amount) |
| 窗口函数 | 对窗口计算,但保留明细行 | row_number() over (...) |
| 存储函数 | 用户定义,可在表达式中调用并返回单值 | calculate_discount(amount) |
函数可以出现在哪里
select upper(username), -- select 表达式
sum(amount) -- 聚合表达式
from orders
where date(created_at) = '2026-08-04' -- where 条件
group by date(created_at) -- 分组键
having sum(amount) > 1000 -- 分组后条件
order by lower(username); -- 排序表达式语法正确不代表性能正确。上面的 date(created_at) 可能让普通 created_at 索引无法直接完成范围定位,后文会给出改写方式。
NULL 的通用规则
多数普通函数只要关键参数为 NULL,结果就是 NULL:
select upper(null); -- NULL
select 10 + null; -- NULL
select concat('A', null); -- NULL聚合函数通常忽略 NULL,但 count(*) 统计行:
select count(*), -- 统计全部行
count(mobile), -- 只统计 mobile 非 NULL 的行
sum(amount), -- 忽略 amount 为 NULL 的行
avg(amount)
from orders;空结果集的 sum、avg 可能返回 NULL。业务要求零时应明确处理:
select coalesce(sum(amount), 0)
from orders
where user_id = ?;不要为了方便把所有 NULL 都强行转换成空字符串或零。NULL、空字符串和零可能代表三种不同业务状态。
数值函数
常用函数
| 函数 | 作用 | 示例结果 |
|---|---|---|
abs(x) | 绝对值 | abs(-12) → 12 |
ceil(x) / ceiling(x) | 向上取整 | ceil(1.2) → 2 |
floor(x) | 向下取整 | floor(1.8) → 1 |
round(x, d) | 四舍五入到指定小数位 | round(12.345, 2) |
truncate(x, d) | 直接截断到指定小数位 | truncate(12.349, 2) |
mod(x, y) | 取余 | mod(10, 3) → 1 |
power(x, y) | 幂 | power(2, 10) → 1024 |
sqrt(x) | 平方根 | sqrt(9) → 3 |
greatest(a, b, ...) | 返回最大参数 | greatest(3, 8, 5) |
least(a, b, ...) | 返回最小参数 | least(3, 8, 5) |
rand() | 随机数 | 每次或每行调用可能不同 |
ROUND 与 TRUNCATE
select round(12.345, 2), -- 舍入
truncate(12.345, 2); -- 截断金额字段应使用 decimal,不能因为使用 round 就把 double 变成精确金额类型。计算顺序、精度和舍入规则应由业务统一定义。
RAND 的边界
select *
from products
order by rand()
limit 10;大表 order by rand() 通常需要为大量候选行生成随机值并排序,成本很高。抽样可以根据主键范围、预生成随机键、采样表或业务缓存设计,不能把它当成通用随机抽取方案。
字符串函数
长度函数
select length('你好'),
char_length('你好');在 utf8mb4 下:
length返回字节数。char_length/character_length返回字符数。
不要使用 length 校验“最多 20 个字符”的业务规则,否则中文、Emoji 和英文的结果不同。
拼接
select concat(first_name, ' ', last_name),
concat_ws('-', province, city, district);concat任意参数为 NULL 时通常返回 NULL。concat_ws会忽略分隔符后的 NULL 参数,但不会自动把所有空字符串当 NULL。
需要明确默认值时:
select concat(coalesce(first_name, ''), ' ', coalesce(last_name, ''));截取与定位
select substring('abcdef', 2, 3), -- bcd
left('abcdef', 2), -- ab
right('abcdef', 2), -- ef
locate('cd', 'abcdef'),
instr('abcdef', 'cd');MySQL 字符位置通常从 1 开始,不是 Java 常见的从 0 开始。
清理与填充
select trim(' mysql '),
ltrim(' mysql'),
rtrim('mysql '),
lpad('42', 5, '0'),
rpad('A', 3, '-');trim 不能替代完整的数据清洗。全角空格、不可见字符、Unicode 规范化和换行符可能需要更明确的规则。
替换、大小写与重复
select replace('a-b-c', '-', '_'),
lower('MySQL'),
upper('mysql'),
reverse('abc'),
repeat('ab', 3);lower、upper 的结果和比较语义受到字符集、collation 与语言规则影响。数据库列如果使用不区分大小写的 collation,查询可能无需 lower(column) 就已经大小写不敏感。
字符串比较与排序规则
select 'Tom' = 'tom' collate utf8mb4_0900_ai_ci;不要为了大小写不敏感查询无条件写:
where lower(username) = lower(?)这可能影响普通索引使用。应先确定列的 collation 是否已经提供目标语义,或者建立与查询表达式一致的函数索引。
日期与时间函数
当前时间
select now(),
current_timestamp(),
curdate(),
current_date(),
curtime(),
current_time(),
utc_timestamp();now() / current_timestamp() 通常在一条语句内保持同一时间值,适合审计字段:
update orders
set status = 'PAID',
paid_at = current_timestamp
where id = ?
and status = 'PENDING';sysdate() 的取值语义与 now() 不完全相同,并涉及复制确定性问题;普通业务优先使用 current_timestamp,不要混用后假设它们完全等价。
提取日期部分
select year(created_at),
month(created_at),
day(created_at),
hour(created_at),
extract(year_month from created_at),
dayofweek(created_at),
last_day(created_at)
from orders;这些函数适合结果展示或统计表达,但放在大表过滤条件左侧可能影响索引定位。
日期加减
select date_add('2026-08-04', interval 7 day),
date_sub('2026-08-04', interval 1 month),
'2026-08-04' + interval 2 hour;月份加减需要注意月末,例如从 1 月 31 日增加一个月的结果不能用“固定增加 30 天”理解。
日期差值
select datediff('2026-08-10', '2026-08-04),
timestampdiff(hour, '2026-08-04 10:00:00', '2026-08-05 12:00:00');datediff主要比较日期天数,会忽略时间部分。timestampdiff可以指定second、minute、hour、day、month、year等单位。
计算年龄不能简单用天数除以 365,闰年和生日是否已过都需要明确业务规则。
格式化和解析
select date_format(created_at, '%Y-%m-%d %H:%i:%s')
from orders;
select str_to_date('2026-08-04 15:30:00', '%Y-%m-%d %H:%i:%s');注意 %i 表示分钟,%m 表示月份。展示格式通常更适合放在应用/API 层;数据库格式化会增加 CPU,也会把日期类型变成字符串,影响排序、比较和复用。
Unix 时间戳
select unix_timestamp(current_timestamp),
from_unixtime(1785835076);Unix 时间戳与会话时区转换、秒或毫秒单位容易混淆。Java 毫秒时间戳直接传给按秒解析的函数会得到错误结果。
条件函数与 NULL 处理
CASE
标准且表达力最强:
select order_id,
case status
when 'PENDING' then '待支付'
when 'PAID' then '已支付'
when 'CLOSED' then '已关闭'
else '未知状态'
end as status_name
from orders;搜索式 case:
select case
when amount >= 1000 then 'HIGH'
when amount >= 100 then 'MEDIUM'
else 'LOW'
end as amount_level
from orders;IF、IFNULL、NULLIF、COALESCE
select if(status = 'PAID', '是', '否'),
ifnull(discount_amount, 0),
nullif(divisor, 0),
coalesce(nickname, username, '匿名用户')
from users;if(condition, true_value, false_value)是 MySQL 常用条件函数。ifnull(a, b)在a为 NULL 时返回b。nullif(a, b)在两者相等时返回 NULL,否则返回a。coalesce(a, b, ...)返回第一个非 NULL 值,SQL 可移植性更好。
避免除零:
select total_amount / nullif(order_count, 0)
from daily_report;类型转换函数
CAST
select cast('123' as signed),
cast('123.45' as decimal(10, 2)),
cast('2026-08-04' as date),
cast(123 as char);CONVERT
select convert('123', signed),
convert(name using utf8mb4)
from users;显式转换通常比依赖隐式转换更清楚,但转换失败、截断和警告行为受 SQL mode 影响。
最危险的是索引列与参数类型不一致:
-- mobile 是 varchar,却传入数字常量
select * from users where mobile = 13800138000;MySQL 可能进行隐式类型转换,造成错误比较或影响索引使用。应用参数类型必须与列类型一致。
聚合函数
常用聚合
select user_id,
count(*) as order_count,
count(distinct product_id) as product_count,
sum(amount) as total_amount,
avg(amount) as avg_amount,
min(amount) as min_amount,
max(amount) as max_amount
from orders
where created_at >= '2026-08-01'
group by user_id;条件聚合
select user_id,
count(*) as total_count,
sum(case when status = 'PAID' then 1 else 0 end) as paid_count,
sum(case when status = 'PAID' then amount else 0 end) as paid_amount
from orders
group by user_id;这通常比为每个状态分别查询一次更高效,也更容易保持同一快照语义。
GROUP_CONCAT
select user_id,
group_concat(order_id order by created_at desc separator ',') as order_ids
from orders
group by user_id;结果长度受 group_concat_max_len 限制。它适合小型展示聚合,不适合把海量关系压成一个字符串返回。字符串结果也不应代替规范的关联表。
JSON 聚合
select user_id,
json_arrayagg(order_id) as order_ids,
json_objectagg(order_id, status) as order_status
from orders
group by user_id;要控制每组数据量,并验证重复 key、NULL 和顺序是否符合目标版本与业务要求。
聚合常见坑
where不能直接过滤聚合结果,应使用having或外层查询。- 非分组非聚合列在
ONLY_FULL_GROUP_BY下必须满足确定性语义。 count(distinct ...)、大分组和排序可能使用临时表并落盘。avg(avg_value)通常不是全局平均值,应该使用总和除以总数。- 聚合结果为空时注意 NULL。
窗口函数
窗口函数在 MySQL 8.0 引入,计算后仍保留明细行。
排名函数
select user_id, order_id, amount,
row_number() over (
partition by user_id
order by amount desc, order_id
) as row_num,
rank() over (
partition by user_id
order by amount desc
) as rank_num,
dense_rank() over (
partition by user_id
order by amount desc
) as dense_rank_num
from orders;row_number每行序号唯一。rank同值同名次,后续名次跳号。dense_rank同值同名次,后续名次不跳号。
前后行
select user_id, created_at, amount,
lag(amount, 1) over (
partition by user_id order by created_at, order_id
) as previous_amount,
lead(amount, 1) over (
partition by user_id order by created_at, order_id
) as next_amount
from orders;适合环比、相邻事件和状态变化分析。
累计值和移动窗口
select user_id, created_at, amount,
sum(amount) over (
partition by user_id
order by created_at, order_id
rows between unbounded preceding and current row
) as running_total
from orders;显式写 rows 窗口框架可以避免同排序值时 range 语义带来的意外结果。
FIRST_VALUE 与 LAST_VALUE
last_value 最常见的坑是默认窗口框架通常只到当前行,所以它可能返回当前行,而不是整个分区最后一行:
select user_id, amount,
last_value(amount) over (
partition by user_id
order by created_at, order_id
rows between unbounded preceding and unbounded following
) as final_amount
from orders;Top N
MySQL 不能在同层 where 直接引用窗口函数结果,应先放入 CTE 或派生表:
with ranked as (
select order_id, user_id, amount,
row_number() over (
partition by user_id
order by amount desc, order_id
) as rn
from orders
)
select *
from ranked
where rn <= 3;窗口函数通常涉及排序,数据量大时应检查执行计划、临时表、排序内存和过滤是否能提前下推。
JSON 函数
创建 JSON
select json_object('id', 1, 'name', 'MySQL'),
json_array('MySQL', 'PostgreSQL', 'Redis');提取值
select json_extract(attributes, '$.brand'),
json_unquote(json_extract(attributes, '$.brand')),
attributes->'$.brand',
attributes->>'$.brand'
from products;->返回 JSON 值。->>通常返回去掉 JSON 引号后的标量文本。
修改 JSON
update products
set attributes = json_set(attributes, '$.color', 'red')
where id = ?;| 函数 | 语义 |
|---|---|
json_set | 路径存在则替换,不存在则新增 |
json_insert | 只新增不存在的路径 |
json_replace | 只替换已存在的路径 |
json_remove | 删除指定路径 |
查询 JSON 结构
select json_valid(attributes),
json_type(attributes),
json_length(attributes),
json_keys(attributes),
json_contains(attributes, json_quote('red'), '$.tags')
from products;JSON_TABLE
MySQL 8 可以使用 json_table 把 JSON 数组展开为关系行:
select p.id, jt.sku_id, jt.quantity
from products p
join json_table(
p.attributes,
'$.items[*]' columns (
sku_id bigint path '$.skuId',
quantity int path '$.quantity'
)
) as jt;JSON 大文档展开可能产生大量行。核心过滤、关联、约束和排序字段仍应优先关系化。
正则表达式函数
MySQL 8 常用:
select regexp_like('abc123', '^[a-z]+[0-9]+$'),
regexp_substr('order-20260804', '[0-9]+'),
regexp_replace('138-0013-8000', '[^0-9]', '');正则适合数据清洗和低频复杂匹配,不适合替代高频结构化条件。正则通常难以利用普通 B+Tree 进行精准定位,大表查询要先用其他条件缩小范围。
用户输入的正则还可能导致极高计算成本,接口应限制长度、复杂度、数据范围和执行时间。
系统与信息函数
select database(),
version(),
connection_id(),
current_user(),
user(),
last_insert_id(),
row_count();区别:
current_user()更接近认证后用于权限检查的账号。user()返回客户端提供的用户和来源信息,二者在账号匹配或代理场景可能不同。last_insert_id()是连接级语义,连接池环境必须在同一物理连接和正确调用时机读取。row_count()与上一条语句、驱动配置和 DML 语义有关,不要跨连接读取。
哈希、加密与 UUID 函数
哈希
select sha2('content', 256),
md5('content');MD5、SHA 哈希不能直接用于安全存储用户密码。密码应在应用层使用专门的慢哈希算法,例如 Argon2、bcrypt 或 scrypt,并使用随机盐和合理成本参数。
AES
select hex(aes_encrypt('sensitive-data', @key)),
aes_decrypt(unhex(@cipher_text), @key);不要在 SQL、仓库或日志中硬编码密钥。真实使用还要明确加密模式、IV、认证完整性、密钥版本和轮换流程。数据库函数加密不能替代 TLS、磁盘与备份加密。
UUID
select uuid(),
uuid_to_bin(uuid()),
bin_to_uuid(uuid_to_bin(uuid()));随机或无序 UUID 作为 InnoDB 聚簇主键会增加索引体积、页分裂和缓存压力。可以使用 binary(16) 减少存储,或选择更有序的 ID 方案。uuid_to_bin(uuid, 1) 的字节交换优化与特定 UUID 版本结构相关,不能对任意随机 UUID 假定都能变成有序主键。
空间函数概览
MySQL 提供 Geometry、Point、Polygon 以及 st_distance、st_contains、st_intersects 等空间函数。使用时要明确:
- SRID 和坐标系。
- 经度、纬度顺序。
- 距离单位。
- 是否使用空间索引。
- 计算使用平面距离还是地球表面距离。
复杂 GIS 场景不能只凭函数名判断正确性,应使用已知坐标样本验证。
函数为什么会影响索引
典型错误
表上有普通索引:
create index idx_orders_created_at
on orders(created_at);查询却写成:
select *
from orders
where date(created_at) = '2026-08-04';普通索引按原始 created_at 值有序,查询条件要求先计算每行的 date(created_at),优化器通常不能直接使用原始 B+Tree 定位目标日期范围。
推荐范围改写
select *
from orders
where created_at >= '2026-08-04 00:00:00'
and created_at < '2026-08-05 00:00:00';半开区间能覆盖当天所有精度的时间值,也避免手写 23:59:59 漏掉更高精度时间。
类似改写:
-- 不推荐
where year(created_at) = 2026
-- 推荐
where created_at >= '2026-01-01'
and created_at < '2027-01-01'不要把函数从列侧机械移到参数侧
如果参数需要转换,可以在应用层或 SQL 常量侧计算一次:
where created_at >= str_to_date(?, '%Y-%m-%d')
and created_at < date_add(str_to_date(?, '%Y-%m-%d'), interval 1 day)但要保证格式、时区和错误输入处理清晰。高频接口更建议应用传入已经类型化的起止时间参数。
函数索引
MySQL 8.0.13 起支持符合限制的函数/表达式索引:
create index idx_users_lower_email
on users ((lower(email)));查询表达式要与索引表达式在语义和类型上匹配:
select id
from users
where lower(email) = lower(?);限制和代价:
- 仅目标版本支持的确定性表达式可用。
- 表达式、返回类型、字符集和 collation 会影响匹配。
- 每次写入都要计算并维护索引。
- 函数索引占用磁盘和 Buffer Pool。
- 如果列 collation 已满足大小写不敏感,额外
lower索引可能是重复设计。 - 最终必须使用
EXPLAIN验证。
生成列替代方案
旧版本或需要显式字段时,可以使用生成列:
alter table orders
add column created_date date
generated always as (date(created_at)) stored,
add index idx_orders_created_date(created_date);查询:
select *
from orders
where created_date = '2026-08-04';生成列让表达式结果可见且可索引,但会增加模型、存储或计算、DDL 和写入维护成本。能够使用原始时间范围查询时,通常没有必要新增日期列。
确定性与非确定性函数
确定性函数对相同输入返回相同结果;非确定性函数可能受时间、随机数、连接状态或服务器状态影响。
| 类型 | 示例 | 风险 |
|---|---|---|
| 通常确定性 | abs(x)、lower(x)、date(x) | 仍受类型、字符集和时区输入影响 |
| 时间相关 | now()、sysdate() | 执行时间和复制语义 |
| 随机相关 | rand()、uuid() | 结果不可重复、排序与复制风险 |
| 会话相关 | last_insert_id()、connection_id() | 必须保持同一物理连接 |
| 环境相关 | database()、current_user() | 数据库和认证上下文不同 |
在生成列、函数索引、复制和存储函数中,确定性尤其重要。
自定义存储函数
存储函数接收参数并返回一个值,可以在 SQL 表达式中调用。
创建示例
delimiter $$
create function calculate_discount(
p_amount decimal(18, 2),
p_rate decimal(5, 4)
)
returns decimal(18, 2)
deterministic
no sql
begin
if p_amount is null or p_rate is null then
return null;
end if;
return round(p_amount * p_rate, 2);
end$$
delimiter ;调用:
select order_id,
calculate_discount(amount, 0.1000) as discount_amount
from orders;删除:
drop function if exists calculate_discount;特征声明
常见声明包括:
deterministic/not deterministicno sqlcontains sqlreads sql datamodifies sql datasql security definer/sql security invoker
这些声明会影响优化、权限理解和 binlog 安全判断,但数据库不能替你证明业务实现真的满足声明。错误地把读取表、依赖时间或随机数的函数声明为 deterministic 会制造错误假设。
DEFINER 与权限
数据库对象通常带有 Definer。迁移到新环境时,如果 Definer 账号不存在或权限不同,调用可能失败;高权限 Definer 还可能扩大普通调用者权限。
查看定义:
show create function calculate_discount;
show function status where db = database();创建和发布存储函数应纳入数据库迁移脚本,不通过个人客户端手工维护。
为什么不要滥用存储函数
存储函数看起来能复用逻辑,但复杂业务大量放入数据库会导致:
- 调用链隐蔽,应用代码难以发现每行调用成本。
- 对大结果集逐行调用,形成数据库 CPU 热点。
- 版本管理、单元测试、灰度和监控困难。
- 权限、Definer、binlog 和复制行为复杂。
- 绑定 MySQL,迁移其他数据库成本高。
- 在函数中读写表可能引入锁、事务和一致性问题。
适合存储函数的逻辑通常应短小、确定、无副作用、输入输出清晰。核心业务状态机仍优先放在 Service,并由数据库约束最终正确性。
函数与复制
Statement 格式 binlog 重放的是 SQL,非确定性函数在主从执行时可能产生不同结果;Row 格式主要记录行变化,通常更能保持结果一致,但日志量可能增加。
需要特别关注:
rand()、uuid()、sysdate()等非确定性行为。- 自定义函数是否真实确定。
- 函数是否读取环境或会话状态。
- 主从时区、字符集和 SQL mode 是否一致。
- 创建函数时的 binlog 安全限制和账号权限。
不要通过关闭安全检查来掩盖函数声明不正确的问题。
函数与性能排查
遇到包含函数的慢 SQL,按下面检查:
- 函数是否作用于索引列的过滤或 Join 条件。
- 是否可以改为原始列的范围或等值条件。
- 函数是否对大量候选行逐行计算。
- 是否因为函数产生排序、临时表或无法下推条件。
- 返回类型、字符集、collation 是否触发隐式转换。
- 聚合或窗口函数的分组、排序数据量有多大。
- JSON_TABLE 或正则是否展开、扫描大量数据。
- 是否需要函数索引、生成列或关系化字段。
- 用
EXPLAIN ANALYZE对比预估与实际行数、循环和耗时。 - 优化后检查写入成本、索引空间和业务结果。
常见错误速查
| 错误 | 后果 | 正确方向 |
|---|---|---|
length 当字符数 | 中文和 Emoji 校验错误 | 使用 char_length |
concat 忽略 NULL 语义 | 任意参数 NULL 导致整个结果 NULL | coalesce 或 concat_ws |
where date(time_col)=? | 可能无法用普通时间索引定位 | 使用半开时间范围 |
where lower(col)=? | 普通索引可能失效 | 正确 collation 或函数索引 |
order by rand() 抽大表 | 全量计算和排序 | 采样键、缓存或专用方案 |
avg 各分片平均值再平均 | 全局平均值错误 | 汇总 sum/count |
last_value 不写窗口框架 | 得到当前行而非分区末行 | 显式指定完整 frame |
日期格式中混淆 %i 和 %m | 分钟、月份结果错误 | 用样本验证格式 |
| 毫秒时间戳当秒传入 | 时间结果严重错误 | 明确单位和时区 |
| MD5 保存密码 | 容易被高速破解 | Argon2/bcrypt/scrypt |
| AES 密钥写 SQL | 日志、仓库泄密 | 密钥系统与轮换 |
| 逐行调用复杂存储函数 | 数据库 CPU 热点 | 集合 SQL、应用层或预计算 |
| 函数声明伪确定性 | 复制和优化假设错误 | 按真实行为声明并测试 |
高频面试题
LENGTH 和 CHAR_LENGTH 有什么区别
length 返回字符串占用的字节数,char_length 返回字符数。在 utf8mb4 下中文和 Emoji 可能占多个字节,所以业务字符长度校验应使用字符语义,而字段存储和索引容量还要关注字节数。
为什么对索引列使用函数可能导致索引失效
B+Tree 按原始列值排序,where date(created_at)=? 要比较的是计算后的日期,普通 created_at 索引不能直接按这个结果定位。优先改成原始列半开范围;表达式确实高频且无法改写时,再评估函数索引或生成列,并用执行计划验证。
COUNT(*)、COUNT(1)、COUNT(column) 有什么区别
count(*) 统计结果行,count(1) 的常量对每行非 NULL,现代 MySQL 通常能做类似优化;count(column) 忽略列为 NULL 的行。面试不要只回答性能,首先要说明语义不同,并通过目标版本和执行计划判断实现。
CASE 与 IF 怎么选
case 属于标准 SQL,支持多分支和更好的跨数据库可移植性;if 是 MySQL 常用函数,简单二选一更简洁。两者都要注意返回分支的类型推导、NULL 和是否造成逐行计算。
NOW 和 SYSDATE 有什么区别
now() 通常表示语句开始时确定的当前时间,在一条语句内保持一致;sysdate() 更接近函数实际执行时刻,可能在同一语句不同调用时返回不同值,并带来复制确定性问题。业务审计时间通常优先 current_timestamp。
聚合函数与窗口函数有什么区别
聚合函数配合 group by 会把多行折叠为每组一行;窗口函数在分区和顺序上计算排名、累计值或相邻值,但保留原始明细行。窗口函数通常需要排序,不能因为写法简洁就忽略内存、临时表和数据量。
自定义存储函数适合放业务逻辑吗
只适合短小、确定、低副作用且确实需要靠近数据的计算。复杂业务放进函数会让调用链、测试、灰度、权限、复制和性能排查更困难;大查询逐行调用还会形成 CPU 热点。核心业务状态机通常放 Service,数据库负责约束和必要的集合运算。
函数索引有什么代价
函数索引可以让特定表达式成为可索引访问路径,但每次写入都要计算和维护,增加空间、Buffer Pool 和写放大;表达式、类型和 collation 还要与查询匹配。能改为原始列范围、等值查询或正确排序规则时,通常更简单。
面试回答模板
MySQL 函数分为标量函数、聚合函数、窗口函数和自定义存储函数。标量函数对每行计算,例如字符串、日期、数值和 JSON 函数;聚合函数把多行汇总;窗口函数在分区内计算但保留明细。使用函数时我会关注 NULL、类型、字符集、时区和确定性,尤其避免在索引列上直接做函数计算,例如 date(created_at)=? 会改成原始时间的半开范围。确实需要按表达式高频检索时,MySQL 8 可以评估函数索引或生成列,但要用 EXPLAIN 验证并评估写入成本。存储函数只用于短小、确定、可测试的计算,不把复杂业务状态机藏进数据库。可运行练习
- 比较
length('你好')和char_length('你好')。 - 比较
round(12.345, 2)和truncate(12.345, 2)。 - 用
coalesce处理空聚合结果。 - 用条件聚合一次统计订单各状态金额。
- 用
row_number查询每个用户金额最高的三笔订单。 - 比较
where date(created_at)=?与时间范围查询的执行计划。 - 在测试库建立函数索引并验证查询表达式是否命中。
- 使用
json_table展开小型 JSON 数组。 - 创建一个无 SQL、确定性的存储函数,再查看
show create function。 - 分别测试 NULL、时区和不同 collation 下的函数结果。
关联知识点
- SQL 基础:SQL 分类、逻辑执行顺序和 CRUD。
- SQL 进阶:CTE、子查询、窗口函数和分页。
- 字符集、排序规则与时区:字符串比较和时间语义。
- JSON、生成列与数据库对象:JSON 索引、视图、过程和触发器。
- MySQL 索引:B+Tree、联合索引、函数导致的索引问题。
- EXPLAIN:验证函数查询的执行计划与实际成本。
本章小结
函数让 SQL 具备计算、清洗、聚合和分析能力,但函数不是越多越好。高频查询要优先保持条件可索引、类型一致、时区和 collation 明确;聚合和窗口函数要控制输入规模;JSON、正则和存储函数要清楚能力边界。面试时既要能写函数,也要能解释它对结果正确性、执行计划、复制和生产维护的影响。
