理解MySQL insert into ... select 的锁情况

MySQL批量导入的锁机制

摘要:
      一直以为"insert into tb select * from tbx" 这样的导入操作是会把tbx表给锁住的,在锁期间是不允许任何操作(保证一致性)。看完这篇写的之后,发现tbx表是会被锁住,但这个锁有2种情况,现在逐一进行分析:

分析
环境:

root@127.0.0.1 : test 02:10:40>select @@global.tx_isolation,@@session.tx_isolation;
+-----------------------+------------------------+
| @@global.tx_isolation | @@session.tx_isolation |
+-----------------------+------------------------+
| REPEATABLE-READ       | REPEATABLE-READ        |
+-----------------------+------------------------+
1 row in set (0.00 sec)

root@127.0.0.1 : test 02:10:50>select @@version;
+------------+
| @@version  |
+------------+
| 5.6.10-log |
+------------+
1 row in set (0.00 sec)

1:按照主键排序插入的情况

直接插入,不加排序字段(默认):

session1:执行操作,表只有5W条记录
root@127.0.0.1 : test 02:10:51>insert into uu select * from user;

session2:查看操作锁的情况(锁的行数)
root@127.0.0.1 : (none) 02:13:30>pager grep "lock(s)"
PAGER set to 'grep "lock(s)"'root@127.0.0.1 : (none) 02:18:08>show engine innodb status; #被锁的行数逐步增加
274 lock struct(s), heap size 31160, 17746 row lock(s), undo log entries 17474root@127.0.0.1 : (none) 02:18:16>show engine innodb status;
500 lock struct(s), heap size 63928, 32572 row lock(s), undo log entries 32074root@127.0.0.1 : (none) 02:18:17>show engine innodb status;
676 lock struct(s), heap size 80312, 44308 row lock(s), undo log entries 43635

用主键升序插入:
  情况和1一样。即默认的"select * from tb" 和 "select * from tb order id(PK) ASC " 是一样的情况。

用主键倒序插入:
  情况和1一样。即默认的"select * from tb" 和 "select * from tb order id(PK) DESC" 是一样的情况,这里说的一样是锁方式一样(都是逐步,只是顺序不一样)。

从上面可知:通过主键排序或则不加排序字段的导入操作"insert into tb select * from tbx",是会锁tbx表,但他的锁是逐步地锁定已经扫描过的记录

2:按照非主键排序插入的情况

session1:执行操作
root@127.0.0.1 : test 02:33:00>insert into uu select * from user order by createTime ;

session2:查看操作锁的情况(行数)
root@127.0.0.1 : (none) 02:27:29>pager grep "lock(s)"
root@127.0.0.1 : (none) 02:27:54>show engine innodb status;  #被锁的行数一样,不变(整张表)
773 lock struct(s), heap size 80312, 50771 row lock(s), undo log entries 1843root@127.0.0.1 : (none) 02:33:19>show engine innodb status;
773 lock struct(s), heap size 80312, 50771 row lock(s), undo log entries 17680root@127.0.0.1 : (none) 02:33:20>show engine innodb status;
773 lock struct(s), heap size 80312, 50771 row lock(s), undo log entries 22260root@127.0.0.1 : (none) 02:33:21>show engine innodb status;
773 lock struct(s), heap size 80312, 50771 row lock(s), undo log entries 28960

从上面可知:通过非主键排序的导入操作"insert into tb select * from tbx",是会锁tbx表,但他的锁是一开始就会锁定整张表

总之,"insert into tb select * from tbx" 的导入操作是会锁定原表,但是锁是有2种情况:“逐步锁”,“全锁”。

验证:

针对1的情况:逐步锁定扫描过的记录,那操作未扫描的数据会怎么样?

session1:执行操作
root@127.0.0.1 : test 02:55:27>insert into uu select * from user;
Query OK, 49998 rows affected (9.06 sec)

session2:测试操作锁的情况
root@127.0.0.1 : test 02:54:49>delete from user where id = 33333;update user set username='TEST' where id = 44444;insert into user(id,username,company) values(1000,'ASD','ABCASDA');
Query OK, 0 rows affected (0.00 sec) #可以删除未扫描(锁)的数据(id=33333)

Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0 #可以更新为扫描(锁)的数据(id=44444)

Query OK, 1 row affected (8.09 sec)#插入(更新,删除)操作被锁了,因为该记录已经被扫描到(id=1000)

session3:查看操作的锁情况:
root@127.0.0.1 : (none) 02:55:33>show engine innodb status;
LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s)
272 lock struct(s), heap size 31160, 17574 row lock(s), undo log entries 17305
1 row in set (0.09 sec)

root@127.0.0.1 : (none) 02:55:35>show engine innodb status;
LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s)
448 lock struct(s), heap size 47544, 29109 row lock(s), undo log entries 28664
1 row in set (0.01 sec)

root@127.0.0.1 : (none) 02:55:37>show engine innodb status;
LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s)
612 lock struct(s), heap size 63928, 40034 row lock(s), undo log entries 39425
1 row in set (0.00 sec)

root@127.0.0.1 : (none) 02:55:39>show engine innodb status;
1 row in set (0.01 sec)

从上面看出,刚好说明了1的情况:逐步的锁定已经扫描过的记录。
默认、主键升序的select :从第一行开始扫描到最后,即第一行开始锁直到最后。
主键倒序select             :从最后一行开始扫描到最前,即最后一行开始锁直到第一行。

针对2的情况:锁定整张表,那就是表锁;不能进行任何操作,直到锁释放了?

session1:执行操作
root@127.0.0.1 : test 03:23:06>insert into uu select * from user order by company;
Query OK, 49994 rows affected (13.70 sec)

session2:测试操作锁的情况
root@127.0.0.1 : test 03:22:44>delete from user where id = 33337;update user set username='TESAAST' where id = 44443;insert into user(id,username,company) values(1000,'ASD','ABCASDA');
Query OK, 1 row affected (9.58 sec)  #直接被锁住了,等待session1释放了。

Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0 #同上

Query OK, 1 row affected (0.00 sec) #同上

session3:查看操作的锁情况:
root@127.0.0.1 : (none) 03:22:45>pager grep "lock(s)"
PAGER set to 'grep "lock(s)"'
root@127.0.0.1 : (none) 03:23:20>show engine innodb status;
773 lock struct(s), heap size 80312, 50765 row lock(s), undo log entries 4433
1 row in set (0.02 sec)

root@127.0.0.1 : (none) 03:23:28>show engine innodb status;
LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s)
773 lock struct(s), heap size 80312, 50765 row lock(s), undo log entries 25383
1 row in set (0.06 sec)

root@127.0.0.1 : (none) 03:23:32>show engine innodb status;
LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s)
773 lock struct(s), heap size 80312, 50765 row lock(s), undo log entries 42464
1 row in set (0.01 sec)

从上面看出,刚好说明了2的情况:一开始就会锁定整张表的记录,不能进行任何操作,直到锁释放了

总结:
类似"insert into tb select * from tbx" 的操作,最好确保tbx表不被做dml操作,不然很可能出现锁等待的情况。另:通过设置隔离级别:read committed & ROW(binlog_format)可以让dml和该语句并发操作。

<think>嗯,用户想了解MySQL 8.0.25中使用INSERT INTO SELECT语句时涉及的next-key行为以及解决方法。我需要先回忆一下MySQL机制,尤其是InnoDB的next-key。 首先,next-key是InnoDB在可重复读隔离级别下用来防止幻读的一种机制。它结合了记录和间隙定一个范围。当执行INSERT INTO SELECT时,如果源有数据被扫描,可能会对扫描到的范围加next-key,特别是当使用唯一索引或非唯一索引的时候。 然后,用户提到的版本是8.0.25,需要确认这个版本是否有相关的变化。根据引用[2],MySQL 8.0.22开始,DML操作读取grant时不加读,但这里讨论的是普通的操作。可能需要查看MySQL官方文档关于的部分。 接下来,INSERT INTO SELECT在复制数据时,如果目标有触发器或外键约束,或者源有索引扫描,可能会导致更长时间的定,从而引发性能问题甚至死。比如,当从大SELECT数据插入到另一个时,如果源的索引范围被定,其他事务可能被阻塞。 解决方法可能包括调整事务隔离级别,比如使用读已提交来减少间隙的使用,或者优化索引,减少扫描范围。另外,分批处理数据可能有助于减少的持有时间。引用[3]提到MySQL 8.0提高了可靠性,可能在管理方面有所优化,但具体到8.0.25需要确认。 用户可能遇到的情况是,在执行INSERT INTO SELECT时,源被长时间定,影响其他事务。需要解释这种情况下next-key的行为,并提供具体的解决方案,比如使用主键顺序扫描、降低隔离级别、拆分事务等。同时,结合用户提供的引用,可能引用相关版本文档来支持解答。</think>### MySQL 8.0.25中`INSERT INTO SELECT`的Next-Key行为分析 #### 1. Next-Key的基本机制 在InnoDB存储引擎中,`INSERT INTO SELECT`语句在**可重复读(REPEATABLE READ)**隔离级别下,若涉及索引范围扫描,可能触发Next-Key。Next-Key是记录(Record Lock)和间隙(Gap Lock)的组合,用于防止幻读(Phantom Read)[^2]。 例如: ```sql INSERT INTO target_table SELECT * FROM source_table WHERE indexed_column BETWEEN 100 AND 200; ``` 如果`source_table`的`indexed_column`是非唯一索引,InnoDB会对该范围内所有记录及间隙加,阻止其他事务插入符合此范围的新数据。 #### 2. 具体行为现 - **主键/唯一索引扫描**:若`SELECT`条件使用主键或唯一索引精确匹配(如`WHERE id = 5`),仅加记录。 - **非唯一索引扫描**:若使用非唯一索引或全扫描,会对扫描范围加Next-Key。 - **无索引扫描**:全扫描时,可能对中所有间隙加,导致严重竞争[^3]。 #### 3. 典型问题场景 ```sql -- 事务1(长时间运行) INSERT INTO orders_archive SELECT * FROM orders WHERE create_time >= '2023-01-01'; -- 事务2(被阻塞) INSERT INTO orders (create_time) VALUES ('2023-01-02'); ``` 若`orders.create_time`有非唯一索引,事务1会定`2023-01-01`之后的间隙,导致事务2被阻塞。 #### 4. 优化解决方案 ##### 方法1:调整隔离级别 将事务隔离级别改为**读已提交(READ COMMITTED)**,该级别下InnoDB不使用间隙: ```sql SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; BEGIN; INSERT INTO target_table SELECT ...; COMMIT; ``` 注意:需评估幻读风险是否可接受。 ##### 方法2:索引优化 为`SELECT`条件添加高效索引,减少定范围: ```sql ALTER TABLE source_table ADD INDEX idx_column(column); ``` ##### 方法3:分批处理 使用主键范围分批操作,减少单次持有时间: ```sql INSERT INTO target_table SELECT * FROM source_table WHERE id BETWEEN 1 AND 1000; INSERT INTO target_table SELECT * FROM source_table WHERE id BETWEEN 1001 AND 2000; ``` ##### 方法4:使用`LIMIT`与循环 通过应用层控制分批逻辑: ```python last_id = 0 batch_size = 1000 while True: cursor.execute(f""" INSERT INTO target_table SELECT * FROM source_table WHERE id > {last_id} ORDER BY id LIMIT {batch_size} """) last_id = get_last_insert_id() if affected_rows < batch_size: break ``` #### 5. 监控状态 通过以下命令实时监控行为: ```sql -- 查看当前信息 SELECT * FROM performance_schema.data_locks; -- 查看阻塞关系 SELECT * FROM sys.innodb_lock_waits; ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值