insert语句的锁

insert ... select 语句

sql 复制代码
CREATE TABLE `t`
(
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `c`  int(11) DEFAULT NULL,
    `d`  int(11) DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `c` (`c`)
) ENGINE=InnoDB;

insert into t
values (null, 1, 1);
insert into t
values (null, 2, 2);
insert into t
values (null, 3, 3);
insert into t
values (null, 4, 4);

create table t2 like t;


insert into t2(c,d) select c,d from t;
-- 在可重复读隔离级别下,这个语句会给 select 的表里扫描到的记录和间隙加读锁


insert into t2(c,d)  (select c+1, d from t force index(c) order by c desc limit 1);
-- 执行流程:从表 t 中按照索引 c 倒序,扫描第一行,拿到结果写入到表 t2 中
-- 加锁范围:表 t 索引 c 上的 (3,4] 和 (4,supremum] 这两个 next-key lock,以及主键索引上 id=4 这一行。


insert into t(c,d)  (select c+1, d from t force index(c) order by c desc limit 1);
-- 慢查询日志: Rows_examined 的值是 5
-- Explain 结果里的 rows=1 是因为受到了 limit 1 
-- 这个语句执行前后,Innodb_rows_read 的值增加了 4。因为默认临时表是使用 Memory 引擎的,所以这 4 行查的都是表 t,也就是说对表 t 做了全表扫描。
-- 执行过程: 
-- 创建临时表,表里有两个字段 c 和 d。
-- 按照索引 c 扫描表 t,依次取 c=4、3、2、1,然后回表,读到 c 和 d 的值写入临时表。这时,Rows_examined=4。
-- 由于语义里面有 limit 1,所以只取了临时表的第一行,再插入到表 t 中。这时,Rows_examined 的值加 1,变成了 5。

-- 这个语句会导致在表 t 上做全表扫描,并且会给索引 c 上的所有间隙都加上共享的 next-key lock。所以,这个语句执行期间,其他事务不能在这个表上插入数据。

-- Extra 字段可以看到"Using temporary"字样,表示这个语句用到了临时表。
-- 这个语句的执行为什么需要临时表,原因是这类一边遍历数据,一边更新数据的情况,如果读出来的数据直接写回原表,就可能在遍历过程中,读到刚刚插入的记录,新插入的记录如果参与计算逻辑,就跟语义不符。
-- 优化
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 ... select 是很常见的在两个表之间拷贝数据的方法。在可重复读隔离级别下,这个语句会给 select 的表里扫描到的记录和间隙加读锁。

如果 insert 和 select 的对象是同一个表,则有可能会造成循环写入。这种情况下,我们需要引入用户临时表来做优化。

  • insert 语句如果出现唯一键冲突,会在冲突的唯一值上加共享的 next-key lock(S 锁)。因此,碰到由于唯一键约束导致报错后,要尽快提交或回滚事务,避免加锁时间过长。

  • insert into ... on duplicate key update 这个语义的逻辑是,插入一行数据,如果碰到唯一键约束,就执行后面的更新语句。

    如果有多个列违反了唯一性约束,就会按照索引的顺序,修改跟第一个索引冲突的行。

    执行这条语句的 affected rows 返回的是 2,很容易造成误解。实际上,真正更新的只有一行,只是在代码实现上,insert 和 update 都认为自己成功了,update 计数加了 1, insert 计数也加了 1。

怎么最快地复制一张表

为了避免对源表加读锁,更稳妥的方案是先将数据写到外部文本文件,然后再写回目标表

  • mysqldump 方法

    使用 mysqldump 命令将数据导出成一组 INSERT 语句

    mysqldump -h h o s t − P host -P host−Pport -u$user --add-locks=0 --no-create-info --single-transaction --set-gtid-purged=OFF db1 t --where="a>900" --result-file=/client_tmp/t.sql

    --single-transaction 的作用是,在导出数据的时候不需要对表 db1.t 加表锁,而是使用 START TRANSACTION WITH CONSISTENT SNAPSHOT 的方法;

    --add-locks 设置为 0,表示在输出的文件结果里,不增加" LOCK TABLES t WRITE;" ;

    --no-create-info 的意思是,不需要导出表结构;

    --set-gtid-purged=off 表示的是,不输出跟 GTID 相关的信息;

    --result-file 指定了输出文件的路径,其中 client 表示生成的文件是在客户端机器上的。

    通过下面这条命令,将这些 INSERT 语句放到 db2 库里去执行。

    mysql -h127.0.0.1 -P13000 -uroot db2 -e "source /client_tmp/t.sql"

  • 导出 CSV 文件

    MySQL 提供了下面的语法,用来将查询结果导出到服务端本地目录:

    select * from db1.t where a>900 into outfile '/server_tmp/t.csv';

相关推荐
float_六七1 小时前
IntelliJ IDEA双击Ctrl的妙用
java·ide·intellij-idea
能摆一天是一天3 小时前
JAVA stream().flatMap()
java·windows
睡觉的时候不会困3 小时前
Redis 主从复制详解:原理、配置与主从切换实战
数据库·redis·bootstrap
颜如玉3 小时前
🤲🏻🤲🏻🤲🏻临时重定向一定要能重定向🤲🏻🤲🏻🤲🏻
java·http·源码
程序员的世界你不懂5 小时前
【Flask】测试平台开发,新增说明书编写和展示功能 第二十三篇
java·前端·数据库
星空寻流年5 小时前
设计模式第一章(建造者模式)
java·设计模式·建造者模式
自学也学好编程5 小时前
【数据库】Redis详解:内存数据库与缓存之王
数据库·redis
gb42152876 小时前
java中将租户ID包装为JSQLParser的StringValue表达式对象,JSQLParser指的是?
java·开发语言·python
JAVA不会写6 小时前
在Mybatis plus中如何使用自定义Sql
数据库·sql
IT 小阿姨(数据库)6 小时前
PgSQL监控死元组和自动清理状态的SQL语句执行报错ERROR: division by zero原因分析和解决方法
linux·运维·数据库·sql·postgresql·centos