1959 字
约 6 分钟
7
S11-J06 · SQL 基础与函数速通:从建表到聚合
2024-12-31
2026-11-07

适用读者:能写 SELECT 但不熟悉 DDL/函数/字符集细节的人;要补"命令背后的语义"的人。
前置:S11-B01(一条 SQL 怎么被执行)、B03(查询基础)、S08(MyBatis)。
覆盖旧文:2782–2815(MySQL 教程命令碎片 30 篇)+ 2792(函数)+ 2802(基本语法)+ 2812(运算符)→ 36 篇 / 约 33 万字符。
扩展点:E-S11-019(DDL 五类语句的边界与锁行为)、E-S11-020(函数与运算符的三类陷阱:NULL、隐式转换、时区)、E-S11-021(字符集与排序规则的实际影响)。
下一篇:S11-J07 · 多表查询、子查询与聚合。


1. 一句话结论

旧教程把 SQL 拆成一条条命令来教,于是丢掉了三件事:DDL 的元数据锁、NULL 的三值逻辑、字符集对比较与索引的影响。 这三件事才是生产事故的来源。

出处:A-2782–A-2815(命令碎片)、A-2792(函数)、A-2802(基本语法)、A-2812(运算符)。


2. DDL 五类语句与锁行为(E-S11-019)

CREATE DATABASE shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;  -- 8.0 默认
CREATE TABLE t (id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL) ENGINE=InnoDB;
ALTER TABLE t ADD COLUMN age INT NULL, ALGORITHM=INPLACE, LOCK=NONE;   -- 8.0 支持在线加列
DROP TABLE t;
TRUNCATE TABLE t;      -- DDL,不可回滚,重置自增
语句 是否可回滚 元数据锁(MDL) 典型阻塞场景
CREATE DDL 隐式提交 短暂 名字冲突
ALTER 否(8.0 有 INSTANT/INPLACE 分级) 需 MDL 排他 长事务持有 MDL 读锁 → 后续查询全排队
DROP / TRUNCATE 否 排他 大表 DROP 需清 buffer pool,可能抖动
RENAME 否 排他 —

判据:线上 DDL 的真正风险不是"改表慢",是"拿不到 MDL 导致全表查询排队"——所以变更前必须查 information_schema.innodb_trx 有无长事务。

-- 变更前检查:有没有长事务会挡住 MDL
SELECT trx_id, trx_started, trx_mysql_thread_id, trx_query
FROM information_schema.innodb_trx
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60;

8.0 的三种 ALTER 算法:

算法 是否重建表 是否允许并发 DML 例子
INSTANT 否(只改元数据) ✅ 末尾加列、改默认值(8.0.12+)
INPLACE 可能(重建索引) 多数允许 加索引、改索引
COPY 是(全表复制) ❌ 改列类型、改字符集

3. 数据类型与字符集(E-S11-021)

类型 要点 坑
VARCHAR(n) n 是字符数(utf8mb4 下最多 4n 字节) 索引长度限制:单列索引 ≤ 3072 字节(DYNAMIC 行格式)
CHAR(n) 定长,尾部空格被去掉(比较时) 不要用于变长内容
TEXT/BLOB 行外存储 不能有默认值;大字段拖慢扫描
DECIMAL(p,s) 精确小数 金额必须用它,不要 FLOAT/DOUBLE
DATETIME vs TIMESTAMP 前者不随时区变;后者存 UTC 并按会话时区转换 TIMESTAMP 上限 2038
utf8 vs utf8mb4 utf8 是 3 字节(不能存 emoji) 8.0 默认已是 utf8mb4;老库要迁移

字符集影响的不只是存储:

-- 排序规则决定比较与索引可用性
WHERE name = 'Alice'          -- 若列与常量排序规则不同 → 可能无法用索引(隐式转换)
ORDER BY name                 -- utf8mb4_0900_ai_ci 不区分大小写/重音(ci = case-insensitive)

判据:"查询突然不走索引"的一个隐蔽原因是排序规则不匹配(列 utf8mb4_general_ci,常量按 utf8mb4_0900_ai_ci 比较)。


4. 函数与运算符的三类陷阱(E-S11-020)

4.1 NULL 的三值逻辑

SELECT NULL = NULL;          -- NULL(不是 true)
SELECT NULL <> NULL;         -- NULL
WHERE col <> 'x'             -- 不包含 col IS NULL 的行!
WHERE col IS NOT NULL AND col <> 'x'   -- ✅ 正确
COUNT(*) vs COUNT(col)       -- 后者不统计 NULL
SUM/AVG 忽略 NULL            -- AVG 的分母是非 NULL 行数

坑 1:NOT IN (子查询含 NULL) 会返回空集——这是最经典的静默错误。改 NOT EXISTS。

4.2 隐式类型转换

-- phone 是 VARCHAR,索引在 phone 上
WHERE phone = 13800000000        -- ❌ 常量是数字 → 列被转成数字 → 索引失效
WHERE phone = '13800000000'      -- ✅

判据:"字符串列传数字"和"数字列传字符串"都会毁掉索引(见 J02 §索引失效)。

4.3 时间函数与时区

NOW()          -- 会话时区
UTC_TIMESTAMP()-- UTC
CURDATE()      -- 日期部分
DATE_ADD(d, INTERVAL 7 DAY)
TIMESTAMPDIFF(SECOND, a, b)
坑 说明
NOW() 依赖会话 time_zone 应用与 DB 时区不一致 → 统计口径错
WHERE DATE(create_time) = '2026-11-07' 函数包裹列 → 索引失效,改范围查询
TIMESTAMP 存 UTC 展示层要统一转换,别在 SQL 里混用

4.4 常用函数速查(旧教程的函数章节压缩)

类别 函数 备注
字符串 CONCAT、CONCAT_WS、SUBSTRING、REPLACE、TRIM、LPAD CONCAT 遇 NULL 返回 NULL
数值 ROUND、CEIL/FLOOR、MOD、ABS ROUND 是四舍五入(银行家舍入需注意版本)
日期 DATE_FORMAT、STR_TO_DATE、LAST_DAY、DATEDIFF 格式化会影响索引
聚合 COUNT/SUM/AVG/MIN/MAX、GROUP_CONCAT GROUP_CONCAT 默认长度 1024,需调 group_concat_max_len
窗口 ROW_NUMBER/RANK/DENSE_RANK、LAG/LEAD、SUM() OVER 8.0 才有;旧文按 5.7 写的示例需改写
JSON JSON_EXTRACT、->>、JSON_TABLE(8.0) 函数索引可加速(8.0.13+)

5. 分组与排序的三个边界

SELECT dept, COUNT(*) c FROM emp GROUP BY dept HAVING c > 5 ORDER BY c DESC LIMIT 10;
问题 答
WHERE 与 HAVING 差别 WHERE 在分组前过滤行;HAVING 在分组后过滤组
ONLY_FULL_GROUP_BY 8.0 默认开启:SELECT 的非聚合列必须在 GROUP BY 中
ORDER BY 与索引 排序列与索引顺序一致才能免排序;ORDER BY RAND() 必排序 + 全表
LIMIT 深分页 LIMIT 1000000, 20 要扫 100 万行 → 用游标(见 S25-J09 §7.1)

6. 对照实验:utf8 vs utf8mb4

-- MySQL 8.0.36
CREATE TABLE t1 (s VARCHAR(10)) CHARSET=utf8;      -- utf8mb3
INSERT INTO t1 VALUES ('😀');                      -- ❌ Incorrect string value
CREATE TABLE t2 (s VARCHAR(10)) CHARSET=utf8mb4;
INSERT INTO t2 VALUES ('😀');                      -- ✅
SELECT LENGTH('😀'), CHAR_LENGTH('😀');            -- 4, 1

口径:上例为按文档推断的行为(utf8mb3 无法表示 4 字节字符),本机未跑;如需引用请自测。


7. 反例:六种"能跑但会出事"的写法

写法 后果
WHERE DATE(t) = '2026-11-07' 索引失效,全表扫
WHERE phone = 13800000000 隐式转换毁索引
NOT IN (SELECT ...) 子查询含 NULL 返回空集
SELECT * 生产查询 传输放大 + 覆盖索引失效
utf8 存 emoji 插入报错或截断
无长事务检查就 ALTER MDL 阻塞全表查询

8. 自测

  1. 三种 ALTER 算法?哪种允许并发 DML?
  2. 为什么线上 DDL 的主要风险是 MDL?
  3. NOT IN 遇 NULL 会怎样?怎么改?
  4. 字符集/排序规则如何影响索引使用?
  5. LIMIT 1000000,20 为什么慢?
答案
  1. INSTANT/INPLACE/COPY;INSTANT 与多数 INPLACE 允许。
  2. 拿不到 MDL 会让后续查询排队,表现为"整表不可用"。
  3. 返回空集;改 NOT EXISTS。
  4. 列与常量排序规则不一致会触发转换,导致无法用索引。
  5. 需先扫描并丢弃前 100 万行;用游标或二次查询。

9. 下一篇

S11-J07 · 多表查询、子查询与聚合 —— 内/外连接的语义差异、ON 与 WHERE 的位置、子查询的三种形态与性能、GROUP BY 的执行顺序,以及教材章练习题的答案补写。


本篇属于系列《S11 · 数据库与 SQL》。旧文来源:A-2781–2815(命令碎片)、A-2792、A-2802、A-2812;扩展点 E-S11-019/020/021。教材练习原为题干,答案由本系列补写(新增内容)。

S11-J06 · SQL 基础与函数速通:从建表到聚合
https://www.clxhxhhr.top/posts/5336/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。