Skip to content

MySQL 函数大全与性能边界

MySQL 函数可以分成两大类:

  • 内置函数:MySQL 自带的数值、字符串、日期、聚合、窗口、JSON、正则、系统等函数。
  • 存储函数:使用 create function 创建、保存在数据库中的自定义函数。

真正需要掌握的不是函数名称有多少,而是四件事:函数输入输出、NULL 与类型转换、时区和字符集语义、函数放在查询条件中对索引和执行计划的影响。

本文以 MySQL 8.x 为主。部分函数、参数和函数索引能力在 5.7 与不同 8.x 小版本中存在差异,使用前应通过 select version() 和目标环境验证。

函数知识地图

mermaid
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)

函数可以出现在哪里

sql
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:

sql
select upper(null);          -- NULL
select 10 + null;            -- NULL
select concat('A', null);    -- NULL

聚合函数通常忽略 NULL,但 count(*) 统计行:

sql
select count(*),          -- 统计全部行
       count(mobile),     -- 只统计 mobile 非 NULL 的行
       sum(amount),       -- 忽略 amount 为 NULL 的行
       avg(amount)
from orders;

空结果集的 sumavg 可能返回 NULL。业务要求零时应明确处理:

sql
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

sql
select round(12.345, 2),       -- 舍入
       truncate(12.345, 2);    -- 截断

金额字段应使用 decimal,不能因为使用 round 就把 double 变成精确金额类型。计算顺序、精度和舍入规则应由业务统一定义。

RAND 的边界

sql
select *
from products
order by rand()
limit 10;

大表 order by rand() 通常需要为大量候选行生成随机值并排序,成本很高。抽样可以根据主键范围、预生成随机键、采样表或业务缓存设计,不能把它当成通用随机抽取方案。

字符串函数

长度函数

sql
select length('你好'),
       char_length('你好');

utf8mb4 下:

  • length 返回字节数。
  • char_length / character_length 返回字符数。

不要使用 length 校验“最多 20 个字符”的业务规则,否则中文、Emoji 和英文的结果不同。

拼接

sql
select concat(first_name, ' ', last_name),
       concat_ws('-', province, city, district);
  • concat 任意参数为 NULL 时通常返回 NULL。
  • concat_ws 会忽略分隔符后的 NULL 参数,但不会自动把所有空字符串当 NULL。

需要明确默认值时:

sql
select concat(coalesce(first_name, ''), ' ', coalesce(last_name, ''));

截取与定位

sql
select substring('abcdef', 2, 3),  -- bcd
       left('abcdef', 2),          -- ab
       right('abcdef', 2),         -- ef
       locate('cd', 'abcdef'),
       instr('abcdef', 'cd');

MySQL 字符位置通常从 1 开始,不是 Java 常见的从 0 开始。

清理与填充

sql
select trim('  mysql  '),
       ltrim('  mysql'),
       rtrim('mysql  '),
       lpad('42', 5, '0'),
       rpad('A', 3, '-');

trim 不能替代完整的数据清洗。全角空格、不可见字符、Unicode 规范化和换行符可能需要更明确的规则。

替换、大小写与重复

sql
select replace('a-b-c', '-', '_'),
       lower('MySQL'),
       upper('mysql'),
       reverse('abc'),
       repeat('ab', 3);

lowerupper 的结果和比较语义受到字符集、collation 与语言规则影响。数据库列如果使用不区分大小写的 collation,查询可能无需 lower(column) 就已经大小写不敏感。

字符串比较与排序规则

sql
select 'Tom' = 'tom' collate utf8mb4_0900_ai_ci;

不要为了大小写不敏感查询无条件写:

sql
where lower(username) = lower(?)

这可能影响普通索引使用。应先确定列的 collation 是否已经提供目标语义,或者建立与查询表达式一致的函数索引。

日期与时间函数

当前时间

sql
select now(),
       current_timestamp(),
       curdate(),
       current_date(),
       curtime(),
       current_time(),
       utc_timestamp();

now() / current_timestamp() 通常在一条语句内保持同一时间值,适合审计字段:

sql
update orders
set status = 'PAID',
    paid_at = current_timestamp
where id = ?
  and status = 'PENDING';

sysdate() 的取值语义与 now() 不完全相同,并涉及复制确定性问题;普通业务优先使用 current_timestamp,不要混用后假设它们完全等价。

提取日期部分

sql
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;

这些函数适合结果展示或统计表达,但放在大表过滤条件左侧可能影响索引定位。

日期加减

sql
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 天”理解。

日期差值

sql
select datediff('2026-08-10', '2026-08-04),
       timestampdiff(hour, '2026-08-04 10:00:00', '2026-08-05 12:00:00');
  • datediff 主要比较日期天数,会忽略时间部分。
  • timestampdiff 可以指定 secondminutehourdaymonthyear 等单位。

计算年龄不能简单用天数除以 365,闰年和生日是否已过都需要明确业务规则。

格式化和解析

sql
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 时间戳

sql
select unix_timestamp(current_timestamp),
       from_unixtime(1785835076);

Unix 时间戳与会话时区转换、秒或毫秒单位容易混淆。Java 毫秒时间戳直接传给按秒解析的函数会得到错误结果。

条件函数与 NULL 处理

CASE

标准且表达力最强:

sql
select order_id,
       case status
         when 'PENDING' then '待支付'
         when 'PAID' then '已支付'
         when 'CLOSED' then '已关闭'
         else '未知状态'
       end as status_name
from orders;

搜索式 case

sql
select case
         when amount >= 1000 then 'HIGH'
         when amount >= 100 then 'MEDIUM'
         else 'LOW'
       end as amount_level
from orders;

IF、IFNULL、NULLIF、COALESCE

sql
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 可移植性更好。

避免除零:

sql
select total_amount / nullif(order_count, 0)
from daily_report;

类型转换函数

CAST

sql
select cast('123' as signed),
       cast('123.45' as decimal(10, 2)),
       cast('2026-08-04' as date),
       cast(123 as char);

CONVERT

sql
select convert('123', signed),
       convert(name using utf8mb4)
from users;

显式转换通常比依赖隐式转换更清楚,但转换失败、截断和警告行为受 SQL mode 影响。

最危险的是索引列与参数类型不一致:

sql
-- mobile 是 varchar,却传入数字常量
select * from users where mobile = 13800138000;

MySQL 可能进行隐式类型转换,造成错误比较或影响索引使用。应用参数类型必须与列类型一致。

聚合函数

常用聚合

sql
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;

条件聚合

sql
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

sql
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 聚合

sql
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 引入,计算后仍保留明细行。

排名函数

sql
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 同值同名次,后续名次不跳号。

前后行

sql
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;

适合环比、相邻事件和状态变化分析。

累计值和移动窗口

sql
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 最常见的坑是默认窗口框架通常只到当前行,所以它可能返回当前行,而不是整个分区最后一行:

sql
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 或派生表:

sql
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

sql
select json_object('id', 1, 'name', 'MySQL'),
       json_array('MySQL', 'PostgreSQL', 'Redis');

提取值

sql
select json_extract(attributes, '$.brand'),
       json_unquote(json_extract(attributes, '$.brand')),
       attributes->'$.brand',
       attributes->>'$.brand'
from products;
  • -> 返回 JSON 值。
  • ->> 通常返回去掉 JSON 引号后的标量文本。

修改 JSON

sql
update products
set attributes = json_set(attributes, '$.color', 'red')
where id = ?;
函数语义
json_set路径存在则替换,不存在则新增
json_insert只新增不存在的路径
json_replace只替换已存在的路径
json_remove删除指定路径

查询 JSON 结构

sql
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 数组展开为关系行:

sql
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 常用:

sql
select regexp_like('abc123', '^[a-z]+[0-9]+$'),
       regexp_substr('order-20260804', '[0-9]+'),
       regexp_replace('138-0013-8000', '[^0-9]', '');

正则适合数据清洗和低频复杂匹配,不适合替代高频结构化条件。正则通常难以利用普通 B+Tree 进行精准定位,大表查询要先用其他条件缩小范围。

用户输入的正则还可能导致极高计算成本,接口应限制长度、复杂度、数据范围和执行时间。

系统与信息函数

sql
select database(),
       version(),
       connection_id(),
       current_user(),
       user(),
       last_insert_id(),
       row_count();

区别:

  • current_user() 更接近认证后用于权限检查的账号。
  • user() 返回客户端提供的用户和来源信息,二者在账号匹配或代理场景可能不同。
  • last_insert_id() 是连接级语义,连接池环境必须在同一物理连接和正确调用时机读取。
  • row_count() 与上一条语句、驱动配置和 DML 语义有关,不要跨连接读取。

哈希、加密与 UUID 函数

哈希

sql
select sha2('content', 256),
       md5('content');

MD5、SHA 哈希不能直接用于安全存储用户密码。密码应在应用层使用专门的慢哈希算法,例如 Argon2、bcrypt 或 scrypt,并使用随机盐和合理成本参数。

AES

sql
select hex(aes_encrypt('sensitive-data', @key)),
       aes_decrypt(unhex(@cipher_text), @key);

不要在 SQL、仓库或日志中硬编码密钥。真实使用还要明确加密模式、IV、认证完整性、密钥版本和轮换流程。数据库函数加密不能替代 TLS、磁盘与备份加密。

UUID

sql
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_distancest_containsst_intersects 等空间函数。使用时要明确:

  • SRID 和坐标系。
  • 经度、纬度顺序。
  • 距离单位。
  • 是否使用空间索引。
  • 计算使用平面距离还是地球表面距离。

复杂 GIS 场景不能只凭函数名判断正确性,应使用已知坐标样本验证。

函数为什么会影响索引

典型错误

表上有普通索引:

sql
create index idx_orders_created_at
on orders(created_at);

查询却写成:

sql
select *
from orders
where date(created_at) = '2026-08-04';

普通索引按原始 created_at 值有序,查询条件要求先计算每行的 date(created_at),优化器通常不能直接使用原始 B+Tree 定位目标日期范围。

推荐范围改写

sql
select *
from orders
where created_at >= '2026-08-04 00:00:00'
  and created_at <  '2026-08-05 00:00:00';

半开区间能覆盖当天所有精度的时间值,也避免手写 23:59:59 漏掉更高精度时间。

类似改写:

sql
-- 不推荐
where year(created_at) = 2026

-- 推荐
where created_at >= '2026-01-01'
  and created_at <  '2027-01-01'

不要把函数从列侧机械移到参数侧

如果参数需要转换,可以在应用层或 SQL 常量侧计算一次:

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 起支持符合限制的函数/表达式索引:

sql
create index idx_users_lower_email
on users ((lower(email)));

查询表达式要与索引表达式在语义和类型上匹配:

sql
select id
from users
where lower(email) = lower(?);

限制和代价:

  • 仅目标版本支持的确定性表达式可用。
  • 表达式、返回类型、字符集和 collation 会影响匹配。
  • 每次写入都要计算并维护索引。
  • 函数索引占用磁盘和 Buffer Pool。
  • 如果列 collation 已满足大小写不敏感,额外 lower 索引可能是重复设计。
  • 最终必须使用 EXPLAIN 验证。

生成列替代方案

旧版本或需要显式字段时,可以使用生成列:

sql
alter table orders
  add column created_date date
    generated always as (date(created_at)) stored,
  add index idx_orders_created_date(created_date);

查询:

sql
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 表达式中调用。

创建示例

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 ;

调用:

sql
select order_id,
       calculate_discount(amount, 0.1000) as discount_amount
from orders;

删除:

sql
drop function if exists calculate_discount;

特征声明

常见声明包括:

  • deterministic / not deterministic
  • no sql
  • contains sql
  • reads sql data
  • modifies sql data
  • sql security definer / sql security invoker

这些声明会影响优化、权限理解和 binlog 安全判断,但数据库不能替你证明业务实现真的满足声明。错误地把读取表、依赖时间或随机数的函数声明为 deterministic 会制造错误假设。

DEFINER 与权限

数据库对象通常带有 Definer。迁移到新环境时,如果 Definer 账号不存在或权限不同,调用可能失败;高权限 Definer 还可能扩大普通调用者权限。

查看定义:

sql
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,按下面检查:

  1. 函数是否作用于索引列的过滤或 Join 条件。
  2. 是否可以改为原始列的范围或等值条件。
  3. 函数是否对大量候选行逐行计算。
  4. 是否因为函数产生排序、临时表或无法下推条件。
  5. 返回类型、字符集、collation 是否触发隐式转换。
  6. 聚合或窗口函数的分组、排序数据量有多大。
  7. JSON_TABLE 或正则是否展开、扫描大量数据。
  8. 是否需要函数索引、生成列或关系化字段。
  9. EXPLAIN ANALYZE 对比预估与实际行数、循环和耗时。
  10. 优化后检查写入成本、索引空间和业务结果。

常见错误速查

错误后果正确方向
length 当字符数中文和 Emoji 校验错误使用 char_length
concat 忽略 NULL 语义任意参数 NULL 导致整个结果 NULLcoalesceconcat_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 还要与查询匹配。能改为原始列范围、等值查询或正确排序规则时,通常更简单。

面试回答模板

text
MySQL 函数分为标量函数、聚合函数、窗口函数和自定义存储函数。标量函数对每行计算,例如字符串、日期、数值和 JSON 函数;聚合函数把多行汇总;窗口函数在分区内计算但保留明细。使用函数时我会关注 NULL、类型、字符集、时区和确定性,尤其避免在索引列上直接做函数计算,例如 date(created_at)=? 会改成原始时间的半开范围。确实需要按表达式高频检索时,MySQL 8 可以评估函数索引或生成列,但要用 EXPLAIN 验证并评估写入成本。存储函数只用于短小、确定、可测试的计算,不把复杂业务状态机藏进数据库。

可运行练习

  1. 比较 length('你好')char_length('你好')
  2. 比较 round(12.345, 2)truncate(12.345, 2)
  3. coalesce 处理空聚合结果。
  4. 用条件聚合一次统计订单各状态金额。
  5. row_number 查询每个用户金额最高的三笔订单。
  6. 比较 where date(created_at)=? 与时间范围查询的执行计划。
  7. 在测试库建立函数索引并验证查询表达式是否命中。
  8. 使用 json_table 展开小型 JSON 数组。
  9. 创建一个无 SQL、确定性的存储函数,再查看 show create function
  10. 分别测试 NULL、时区和不同 collation 下的函数结果。

关联知识点

本章小结

函数让 SQL 具备计算、清洗、聚合和分析能力,但函数不是越多越好。高频查询要优先保持条件可索引、类型一致、时区和 collation 明确;聚合和窗口函数要控制输入规模;JSON、正则和存储函数要清楚能力边界。面试时既要能写函数,也要能解释它对结果正确性、执行计划、复制和生产维护的影响。