MYSQL:insert...select:为什么锁源表的所有行和间隙?怎么最快地复制一张表?

课程:B站大学

记录学习极客时间团队MySQL45讲,进阶数据分析和数据处理

MySQL主库和备库

  • [insert 语句的锁为什么这么多?](#insert 语句的锁为什么这么多?)
    • 一、insert...select:为什么锁源表的所有行和间隙?
    • [二、insert 循环写入:同表 insert + select 导致全表扫描](#二、insert 循环写入:同表 insert + select 导致全表扫描)
      • [2.1 目标表不同时(正常)](#2.1 目标表不同时(正常))
      • [2.2 目标表相同时(问题)](#2.2 目标表相同时(问题))
      • [优化方案:拆成两步 + 内存临时表](#优化方案:拆成两步 + 内存临时表)
    • [三、insert 唯一键冲突:为什么死锁频发?](#三、insert 唯一键冲突:为什么死锁频发?)
      • [3.1 加锁行为](#3.1 加锁行为)
      • [3.2 经典死锁场景](#3.2 经典死锁场景)
      • 避免建议
    • [四、insert...on duplicate key update](#四、insert...on duplicate key update)
  • 怎么最快地复制一张表?
    • [一、mysqldump 导出 INSERT 语句](#一、mysqldump 导出 INSERT 语句)
    • [二、select...into outfile + load data](#二、select...into outfile + load data)
    • [三、物理拷贝(可传输表空间,MySQL 5.6+)](#三、物理拷贝(可传输表空间,MySQL 5.6+))
    • 对比
    • 思考题
  • 实践是检验真理的唯一标准

insert 语句的锁为什么这么多?

普通 insert 是轻量操作(自增 id 申请后即可释放自增锁),但以下 4 种特殊场景需要额外加锁或无法及时释放锁


一、insert...select:为什么锁源表的所有行和间隙?

场景 :可重复读(RR)隔离级别 + binlog_format=statement

sql 复制代码
insert into t2(c,d) select c,d from t;

原因:保证主备一致

不加锁时,可能出现这样的并发时序:

session A session B
insert into t2 select ... from t(先执行)
insert into t values(-1,-1,-1)(后写入 binlog)

由于 binlog 按提交顺序记录,备库重放时会把 id=-1 也复制到 t2,导致主备不一致

加锁行为

  • 对源表 t 的所有扫描到的记录 + 间隙next-key lock(共享/读锁)
  • 语句执行完才释放,期间阻塞其他事务对 t 的插入

二、insert 循环写入:同表 insert + select 导致全表扫描

2.1 目标表不同时(正常)

sql 复制代码
insert into t2(c,d)
(select c+1, d from t force index(c) order by c desc limit 1);
  • 走索引 c 倒序,只扫描 1 行
  • Rows_examined = 1
  • 加锁范围:索引 c 上的 (3,4](4,supremum] 两个 next-key lock + 主键 id=4 这一行

2.2 目标表相同时(问题)

sql 复制代码
insert into t(c,d)
(select c+1, d from t force index(c) order by c desc limit 1);
  • Rows_examined = 5(不是预期的 1 或 2)
  • Using temporary:需要临时表保存全量数据

完整执行流程

  1. 创建临时表(c, d 两字段)
  2. 按索引 c 扫描表 t,取 c=4,3,2,1 回表写入临时表 → 4 行
  3. limit 1 只取临时表第一行插入 t → +1 行
  4. 总计 5 行

为什么需要临时表?

一边遍历一边写回原表时,会读到自己刚插入的新记录,导致参与计算逻辑、语义错误。临时表就是为了打破这个循环。

加锁代价 :对表 t 全表扫描 + 索引 c 上所有间隙加共享 next-key lock ,期间其他事务无法插入

优化方案:拆成两步 + 内存临时表

sql 复制代码
create temporary table temp_t(c int,d int) engine=memory;
insert into temp_t (select c+1, d from t force index(c) order by c desc limit 1);
insert into t select * from temp_t;
drop table temp_t;

优化点 :先 insert 到临时表只扫描 1 行,再从临时表写回 t,避免全表扫描


三、insert 唯一键冲突:为什么死锁频发?

3.1 加锁行为

关键规则 :insert 遇到唯一键冲突时,不只是报错,还会在冲突的唯一索引上加锁

场景 加锁类型
普通唯一键冲突 冲突索引上加 共享 next-key lock(S 锁)
on duplicate key update 冲突索引上加 排他 next-key lock(X 锁)

⚠️ 官方文档错误纠正 :无论冲突的是主键 还是唯一索引 ,加的都是 next-key lock(不是"主键加记录锁、唯一索引加 next-key lock")。此 bug 已提交官方并被 verified。

为什么加读锁? 防止这条记录被别的事务删除(虽理由不算绝对充分,但目前实现如此)。

3.2 经典死锁场景

时刻 session A session B session C
T1 begin; insert (null,5,5);
T2 insert (null,5,5);(冲突,等 S 锁) insert (null,5,5);(冲突,等 S 锁)
T3 rollback; 获得 S 锁,要加 X 锁插入 获得 S 锁,要加 X 锁插入 → 死锁

死锁产生逻辑

  1. T1:session A 在 c=5 上加记录锁(唯一索引退化为记录锁)
  2. T2:B、C 发现唯一键冲突,各自加读锁(S),等待 A 释放
  3. T3:A rollback → B、C 同时获得 S 锁,都要升级为写锁(X)
  4. S 锁互相持有、X 锁互相等待 → 死锁

避免建议

  • 遇到唯一键冲突报错后,尽快 commit / rollback,缩短锁持有时间
  • 降低高并发下的重复值插入
  • 去掉不必要的唯一值检测(业务允许时)

四、insert...on duplicate key update

sql 复制代码
insert into t values(11,10,10) on duplicate key update d=100;

语义:插入一行,若违反唯一键约束,则执行后面的 update。

加锁 :冲突索引上加 排他 next-key lock(X 锁/写锁)

两个坑

  1. 多个唯一键冲突时 :按索引顺序,只修改与第一个索引冲突的行
  2. affected_rows 返回 2:实际只更新 1 行。因代码实现上 insert 和 update 各计数 +1

怎么最快地复制一张表?

把 db1.t 中 a>900 的数据拷贝到 db2.t,有三种方法。

一、mysqldump 导出 INSERT 语句

bash 复制代码
mysqldump -h$host -P$port -u$user \
  --add-locks=0 \
  --no-create-info \
  --single-transaction \
  --set-gtid-purged=OFF \
  --result-file=/client_tmp/t.sql \
  db1 t --where="a>900"

参数说明:

  • --single-transaction:用一致性快照读,不加表锁
  • --add-locks=0:输出文件里不加 LOCK TABLES t WRITE
  • --no-create-info:不导表结构
  • --set-gtid-purged=off:不输出 GTID 信息
  • --result-file:指定输出路径,文件在客户端机器上

默认一条 INSERT 包含多行 value,写入更快:

想要一行一个 INSERT,加 --skip-extended-insert

导入目标库:

bash 复制代码
mysql -h127.0.0.1 -P13000 -uroot db2 -e "source /client_tmp/t.sql"

source 是客户端命令,不是 SQL。服务端 binlog 和慢日志里记的是那些真正的 INSERT 语句。


二、select...into outfile + load data

导出到服务端本地:

sql 复制代码
select * from db1.t where a>900 into outfile '/server_tmp/t.csv';

注意:

  1. 文件在服务端,不在你执行命令的客户端机器上
  2. 路径受 secure_file_priv 限制(设为 NULL 则禁止导出)
  3. 不能覆盖已有文件,有同名先删
  4. 字段里含换行符会加 \ 转义

导入:

sql 复制代码
load data infile '/server_tmp/t.csv' into table db2.t;

主备同步怎么搞

不能直接把 load data infile '/server_tmp/t.csv' 记进 binlog------备库上没有这个文件。实际流程:

  1. 主库把 csv 内容直接写进 binlog
  2. binlog 里记的是:load data local infile '/tmp/SQL_LOAD_MB-1-0' into table db2.t
  3. 备库 apply 线程从 binlog 读出内容,写到本地 /tmp/SQL_LOAD_MB-1-0
  4. 再执行 load data 把这个本地临时文件读进去

为什么要加 local

  • 不加 local:读服务端文件,必须在 secure_file_priv 允许的目录里
  • 加 local:读客户端(对备库来说就是同步线程自己)本地文件,不受 secure_file_priv 限制

只导数据不导结构,要结构的话用 --tab

bash 复制代码
mysqldump --single-transaction --set-gtid-purged=OFF db1 t --tab=$secure_file_priv

会生成 .sql(建表)和 .txt(数据)两个文件。


三、物理拷贝(可传输表空间,MySQL 5.6+)

直接 cp .ibd 不行,数据字典没注册。正确步骤:

sql 复制代码
-- 1. 建空表
create table db1.r like db1.t;

-- 2. 丢弃新表的表空间(r.ibd 被删)
alter table db1.r discard tablespace;

-- 3. 冻结源表,生成 t.cfg(此时 t 只读)
flush table db1.t for export;

-- 4. 拷贝文件(在服务器上操作)
cp t.cfg r.cfg;
cp t.ibd r.ibd;
chown mysql:mysql r.cfg r.ibd;   -- 不改权限会报 Tablespace is missing

-- 5. 解锁源表(t.cfg 自动删除)
unlock tables;

-- 6. 导入
alter table db1.r import tablespace;

要点:

  • flush table ... for export 之后源表只读,操作完赶紧 unlock
  • import 时每个数据页都要改表空间 id,大文件要一些时间,但比逻辑导入快得多
  • cfg 文件 import 完不会自动删,手动清一下

对比

速度 能过滤数据 跨引擎 局限
物理拷贝 最快 只能全表 仅 InnoDB 要登服务器、要文件权限
mysqldump 支持 where 可以 不能用 join 等复杂条件
outfile 较快 支持任意 SQL 可以 每次一张表,结构单独备

物理拷贝大表最快。逻辑拷贝灵活,能跨引擎。


思考题

binlog 里记录的 load data 带了 local,备库执行时读的是自己本地的 /tmp/SQL_LOAD_MB-1-0。为什么不能不带 local?

因为不带 local 读的是服务端文件,必须在 secure_file_priv 指定的目录里。备库临时文件在 /tmp 下,不一定在允许路径内,执行就会失败。加了 local 走客户端文件读取,绕开这个限制,主备才不会断。

实践是检验真理的唯一标准

相关推荐
SamChan901 小时前
PyMuPDF vs pdfplumber vs pypdf:PDF 文本提取实测对比(翻译预处理视角)
python·ai·pdf
oradh2 小时前
Oracle联机日志文件损坏故障总结
数据库·oracle·oracle联机日志文件损坏
辰辉创聚2 小时前
炎症与免疫相关细胞因子:信号通路、分类及科研检测应用
python·oracle·nycodenz·重组il-6蛋白·抗tnf-α抗体·il-1β蛋白
IvorySQL2 小时前
PostgreSQL 日报|8 字节 TOAST 值支持(9 月 16 日)
数据库·人工智能·postgresql
ai小陈2 小时前
LTX2.5音视频生成任务验收实战:批量记录与音画质量检查
人工智能·python·深度学习·ai·音视频·gpu算力
今儿敲了吗2 小时前
04英文文本关键词提取(TF-IDF)
笔记·python
绘梨衣5472 小时前
PDF跨页表格处理方案(极简落地版 + 工具对比)
python·rag
实验室管理云平台2 小时前
北京盛元广通推疾控中心实验室管理系统,提升检测效率
数据库·python
玫幽倩3 小时前
2026第二届湾区杯网络安全大赛决赛(AI专项赛道静态题wp)
pytorch·python·ai·agent·ctf·rag·湾区杯