SQL常见面试题总结(2)
第二篇转向「写」的一侧:插入的三种形态、带条件的更新与删除、DELETE/TRUNCATE/DROP 的经典辨析,以及建表、改表、索引的创建与删除。这类题在笔试里以手写 DML/DDL 为主,细节全部在约束、NULL 和副作用上。
考察地图
- 插入:多行 VALUES、INSERT … SELECT 迁移、主键冲突处理(REPLACE INTO vs ON DUPLICATE KEY UPDATE)。
- 更新:时间比较与 NULL 判断、列内字符串替换。
- 删除:DELETE 配 ORDER BY/LIMIT 的限量删除、三种「清空」的辨析。
- 表结构:CREATE TABLE 的约束与默认值、ALTER TABLE 子操作速查。
- 索引:四类常见索引的定位、创建与删除语法。
- 建议自行查阅:字段类型选型细节、字符集与排序规则——记忆型内容,面试中多为铺垫。
插入
批量插入和 INSERT … SELECT 怎么写?
结论:多行合并成一条 INSERT ... VALUES (...), (...) 远快于逐条执行;跨表迁移用 INSERT INTO t SELECT ...,查询结果直接灌入目标表。
原理:
-- 一条语句插入两行;自增主键 id 不出现在列清单中 |
逐条插入每条都有一次解析、事务与 binlog 开销,合并后这些成本被摊薄。INSERT … SELECT 要求列按位置对齐、类型兼容;SELECT 端的条件要写索引友好的范围形式,YEAR(start_time) < 2021 这种包函数的写法会让索引失效。
延伸:超大批量迁移建议按主键或时间范围分批提交,避免单个巨型事务撑大 undo、拉长锁持有时间;SELECT 端默认是一致性读,不会长时间锁源表。应用层批量插入时配合 rewriteBatchedStatements=true,JDBC 驱动才会把多条 INSERT 真正合并成一条多值语句,否则「批处理」只是逐条发送的假象。
追问:INSERT … SELECT 在什么情况下会对源表加锁?提示:binlog 格式为 STATEMENT 且隔离级别较低时的一致性读行为差异。
「有则更新、无则插入」用 REPLACE INTO 还是 ON DUPLICATE KEY UPDATE?
结论:优先 INSERT ... ON DUPLICATE KEY UPDATE——它是原地更新;REPLACE INTO 是先删原行再插新行,副作用明显更大。
原理:
INSERT INTO examination_info (exam_id, tag, difficulty, duration, release_time) |
REPLACE 命中主键或唯一键冲突时执行的是 DELETE + INSERT:自增值会涨、删除与插入两类触发器各触发一次、外键级联可能误伤关联数据;ON DUPLICATE KEY UPDATE 只触发更新语义。两者的「冲突判定」都完全依赖表上的主键或唯一索引,没有唯一约束就无从谈起。
延伸:MySQL 8.0.20 起 VALUES(col) 写法被标记废弃,推荐行别名:... AS new ON DUPLICATE KEY UPDATE tag = new.tag。高并发冲突路径下两条语句都可能卷入死锁,压测需覆盖。
追问:没有唯一索引的表上执行这两条会发生什么?提示:永远不冲突,退化为普通插入,重复数据越攒越多。
更新与删除
带时间与 NULL 条件的 UPDATE 有哪些坑?
结论:难点不在语法,而在把自然语言精确翻译成时间比较和 IS NULL——两个高频失分点。
原理:以「把 2021-09-01 之前开始且未完成的记录置为被动完成」为例:
UPDATE exam_record |
「未完成」是 submit_time IS NULL,写 = NULL 永远匹配不到行;时间比较让列直接对常量,能走索引,包一层 DATE(start_time) 就把索引废了。列内局部替换可用函数写法:SET tag = REPLACE(tag, 'PYTHON', 'Python'),适合「改字符串的一部分」的场景。
延伸:大表 UPDATE 先用同条件 SELECT 确认影响行数;MySQL 的 safe-update 模式会拒绝无键条件的 UPDATE/DELETE。大批量更新应分批 + 主键范围推进,控制单事务锁规模。
追问:
UPDATE不带 WHERE 会撞上什么保护?提示:sql_safe_updates 与会话级开关。
DELETE 能配 ORDER BY 和 LIMIT 吗?
结论:MySQL 单表 DELETE 支持 ORDER BY ... LIMIT n,是「删最早/最差的 N 条」的标准写法;多表 DELETE 不支持。
原理:
-- 删除「未完成 或 用时不足 5 分钟」中最早开始的 3 条 |
语义是先按 start_time 升序排队,只删前 3 条命中行。时间差用 TIMESTAMPDIFF(MINUTE, a, b)(b − a,按分钟)最直观;MINUTE(TIMEDIFF(b, a)) 也能算,但 TIMEDIFF 的结果本质是时间间隔,跨度超过 838 小时会被截断,通用性更差。
延伸:分批清理大表的经典模式就是循环执行 DELETE ... LIMIT 5000 直到 affected rows 为 0,每批之间留间隔,把 undo 压力和主从延迟摊平。
追问:分批删除的批大小怎么定?提示:单批锁持有时间、redo 生成速率与复制延迟之间的权衡。
DELETE、TRUNCATE、DROP 有什么区别?
结论:DELETE 逐行删数据(DML,可回滚,自增不重置);TRUNCATE 清空整表并重置自增(DDL,隐式提交);DROP 连表结构带数据一起删。
原理:
TRUNCATE TABLE exam_record; -- 清空数据,AUTO_INCREMENT 归位 |
DELETE 每行都写 undo/redo,事务内可 ROLLBACK;删完自增计数器不动,要重置得再执行 ALTER TABLE t AUTO_INCREMENT = 1。TRUNCATE 本质是重建表,页直接回收,速度远超全量 DELETE,但不能带 WHERE,且在 MySQL 里会隐式提交当前事务。「删除全部记录并重置自增主键」正是 TRUNCATE 的招牌场景。DROP 则把定义与数据一并移除,空间归还给操作系统。
延伸:有外键引用的父表不能直接 TRUNCATE,需先清空子表或临时关闭 foreign_key_checks。主从架构下,全量 DELETE 会在从库逐行重放造成明显延迟,TRUNCATE 作为 DDL 则只传一条语句——这也是「清大表宁 TRUNCATE 不 DELETE」的运维共识。MySQL 8 的 DDL 原子性保证了 DROP/TRUNCATE 崩溃后不留半成品状态。
追问:TRUNCATE 为什么快?提示:绕过逐行 undo/redo,直接释放数据页、重建段。
表结构与索引
手写 CREATE TABLE 要交代哪些要素?
结论:主键、唯一约束、NOT NULL、默认值、自增、列注释与字符集,是手写建表题的主要采分点。
原理:
CREATE TABLE IF NOT EXISTS user_info_vip ( |
要点:自增列必须是键;NOT NULL 需显式声明;DEFAULT CURRENT_TIMESTAMP 让插入时省略该列;IF NOT EXISTS 防止重复执行报错;字符集用 utf8mb4 才算真正的 UTF-8(MySQL 的 utf8 仅 3 字节,存不下 emoji);level 这类与保留字重名的列要加反引号。
延伸:生产环境还会显式写 ENGINE=InnoDB(5.5 起默认)。VARCHAR(64) 的 64 按字符计而非字节,是常见口误点。
追问:utf8 和 utf8mb4 的索引长度限制差在哪?提示:每字符最大字节数影响前缀索引的最大列长。
ALTER TABLE 的常用子操作有哪些?
结论:ADD 加列(可指定位置)、CHANGE 改名并可改类型、MODIFY 只改类型与约束、DROP 删列、RENAME 改表名;多个子操作逗号连写、一次执行。
原理:
ALTER TABLE user_info |
CHANGE 必须写全新旧两个列名(不改名也得写两遍),MODIFY 不能改名;COLUMN 关键字可省略。连写成一条的实益是表只重建一次,逐条执行每条都可能触发整表拷贝。
延伸:MySQL 8 的 INSTANT ADD COLUMN(末尾加列、只改元数据)让加列秒级完成;改类型仍是 INPLACE 或 COPY 算法。生产大表结构变更通常交给 gh-ost / pt-osc 做无锁在线变更。
追问:ALGORITHM=INSTANT / INPLACE / COPY 三档差别在哪?提示:是否重建表、是否需回放增量 DML。
如何快速复制一张已有表?
结论:只要结构用 CREATE TABLE t LIKE s;结构加数据用 CREATE TABLE t AS SELECT ...,但后者会丢索引、约束与默认值。
原理:
CREATE TABLE user_info_vip LIKE user_info; -- 完整复制结构与索引,不带数据 |
LIKE 走元数据层面的整结构复制,主键、索引、列注释都保留,适合做空表拆分;AS SELECT 本质是把查询结果物化成新表,列类型按表达式推导,自增、默认值、索引全部丢失,只适合做临时分析表。两者都可以再叠加 INSERT INTO ... SELECT 补数据。
延伸:备份某时间窗口的历史数据时,常见组合就是「LIKE 建空表 + INSERT … SELECT 带范围条件灌数据」。跨库复制时带上库名前缀即可,注意字符集要显式对齐。
追问:
AS SELECT拷过来的表自增列会变成什么?提示:退化为普通 INT 列,需要再 ALTER 补 AUTO_INCREMENT。
常见索引类型有哪些?怎么创建和删除?
结论:常用四类——B-Tree 普通索引、唯一索引、主键索引、全文索引;创建用 CREATE INDEX 或 ALTER TABLE ADD,删除一次只能删一个。
原理:
ALTER TABLE examination_info |
B-Tree 索引支撑等值、范围与排序;唯一索引额外约束列值唯一(NULL 不参与唯一性冲突,可有多行 NULL);主键索引即 InnoDB 的聚簇索引,每张表一个;全文索引面向关键词检索,MySQL 8 处理中文需配 ngram 解析器。
延伸:二级索引叶子节点存的是主键值,「回表」由此而来;查询列全部落在索引内即覆盖索引,可省回表。建索引前先看基数(SHOW INDEX 的 Cardinality),低基数列单独建索引收益甚微。
追问:为什么性别这类列通常不建索引?提示:选择度过低,优化器宁走全表扫描。

