mysql insert into select 语句为啥会造成死锁

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql insert into select 语句为啥会造成死锁相关的知识,希望对你有一定的参考价值。

对于MySQL来说,有三种锁的级别:页级、表级、行级

页级的典型代表引擎为BDB。

表级的典型代表引擎为MyISAM,MEMORY以及很久以前的ISAM。

行级的典型代表引擎为INNODB。

-我们实际应用中用的最多的就是行锁。

行级锁的优点如下:

1)、当很多连接分别进行不同的查询时减小LOCK状态。

2)、如果出现异常,可以减少数据的丢失。因为一次可以只回滚一行或者几行少量的数据。

行级锁的缺点如下:

1)、比页级锁和表级锁要占用更多的内存。

2)、进行查询时比页级锁和表级锁需要的I/O要多,所以我们经常把行级锁用在写操作而不是读操作。

3)、容易出现死锁。

对于写锁定如下:

1)、如果表没有加锁,那么对其加写锁定。

2)、否则,那么把请求放入写锁队列中。

对于读锁定如下:

1)、如果表没有加写锁,那么加一个读锁。

2)、否则,那么把请求放到读锁队列中。

当然我们可以分别用low_priority 以及high_priority在写和读操作上来改变这些行为。

如果想要在一个表上做大量的 INSERT 和 SELECT 操作,但是并行的插入却不可能时,可以将记录插入到临时表中,然后定期将临时表中的数据更新到实际的表里。可以用以下命令实现:

mysql> LOCK TABLES real_table WRITE, insert_table WRITE;

mysql> INSERT INTO real_table SELECT * FROM insert_table;

mysql> TRUNCATE TABLE insert_table;

mysql> UNLOCK TABLES;

InnoDB 使用行级锁,BDB 使用页级锁。对于 InnoDB 和 BDB 存储引擎来说,是可能产生死锁的。这是因为 InnoDB 会自动捕获行锁,BDB 会在执行 SQL 语句时捕获页锁的,而不是在事务的开始就这么做。

行级锁的优点有:

在很多线程请求不同记录时减少冲突锁。

事务回滚时减少改变数据。

使长时间对单独的一行记录加锁成为可能。

行级锁的缺点有:

比页级锁和表级锁消耗更多的内存。

当在大量表中使用时,比页级锁和表级锁更慢,因为他需要请求更多的所资源。

当需要频繁对大部分数据做 GROUP BY 操作或者需要频繁扫描整个表时,就明显的比其它锁更糟糕。

使用更高层的锁的话,就能更方便的支持各种不同的类型应用程序,因为这种锁的开销比行级锁小多了。

表级锁在下列几种情况下比页级锁和行级锁更优越:

很多操作都是读表。

在严格条件的索引上读取和更新,当更新或者删除可以用单独的索引来读取得到时:

UPDATE tbl_name SET column=value WHERE unique_key_col=key_value;

DELETE FROM tbl_name WHERE unique_key_col=key_value;

SELECT 和 INSERT 语句并发的执行,但是只有很少的 UPDATE 和 DELETE 语句。

很多的扫描表和对全表的 GROUP BY 操作,但是没有任何写表。

表级锁和行级锁或页级锁之间的不同之处还在于:

将同时有一个写和多个读的地方做版本(例如在MySQL中的并发插入)。也就是说,数据库/表支持根据开始访问数据时间点的不同支持各种不同的试图。其它名有:时间行程,写复制,或者是按需复制。

复制代码 代码如下:

//执行SQL语句 锁掉stat_num表

$sql = "LOCK TABLES 表名 WRITE"; //表的WRITE锁定,阻塞其他所有mysql查询进程

mysql_query($sql);

//执行更新或写入操作

$sql = "UPDATE stat_num SET `correct_num`=`correct_num`+1 WHERE stat_date='$cur_date'";

mysql_query($sql);

//当前请求的所有写操作做完后,执行解锁sql语句

$sql = "UNLOCK TABLES";

mysql_query($sql);

参考技术A

加锁情况与死锁原因分析

为方便大家复现,完整表结构和数据如下:

CREATE TABLE `t3` (
`c1` int(11) NOT NULL AUTO_INCREMENT,
`c2` int(11) DEFAULT NULL,
PRIMARY KEY (`c1`),
UNIQUE KEY `c2` (`c2`)
) ENGINE=InnoDB

insert into t3 values(1,1),(15,15),(20,20);

在 session1 执行 commit 的瞬间,我们会看到 session2、session3 的其中一个报死锁。这个死锁是这样产生的:

    1. session1 执行 delete  会在唯一索引 c2 的 c2 = 15 这一记录上加 X lock(也就是在MySQL 内部观测到的:X Lock but not gap);

    2. session2 和 session3 在执行 insert 的时候,由于唯一约束检测发生唯一冲突,会加 S Next-Key Lock,即对 (1,15] 这个区间加锁包括间隙,并且被 seesion1 的 X Lock 阻塞,进入等待;

    3. session1 在执行 commit 后,会释放 X Lock,session2 和 session3 都获得 S Next-Key Lock;

    4. session2 和 session3 继续执行插入操作,这个时候 INSERT INTENTION LOCK(插入意向锁)出现了,并且由于插入意向锁会被 gap 锁阻塞,所以 session2 和 session3 互相等待,造成死锁。

    死锁日志如下: 

    INSERT INTENTION LOCK

    在之前的死锁分析第四点,如果不分析插入意向锁,也是会造成死锁的,因为插入最终还是要对记录加 X Lock 的,session2 和 session3 还是会互相阻塞互相等待。

    但是插入意向锁是客观存在的,我们可以在官方手册中查到,不可忽略:

    Prior to inserting the row, a type of gap lock called an insert intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap.

    插入意向锁其实是一种特殊的 gap lock,但是它不会阻塞其他锁。假设存在值为 4 和 7 的索引记录,尝试插入值 5 和 6 的两个事务在获取插入行上的排它锁之前使用插入意向锁锁定间隙,即在(4,7)上加 gap lock,但是这两个事务不会互相冲突等待。

    当插入一条记录时,会去检查当前插入位置的下一条记录上是否存在锁对象,如果下一条记录上存在锁对象,就需要判断该锁对象是否锁住了 gap。如果 gap 被锁住了,则插入意向锁与之冲突,进入等待状态(插入意向锁之间并不互斥)。总结一下这把锁的属性:

    1. 它不会阻塞其他任何锁;

    2. 它本身仅会被 gap lock 阻塞。

    在学习 MySQL 过程中,一般只有在它被阻塞的时候才能观察到,所以这也是它常常被忽略的原因吧...

    GAP LOCK

    在此例中,另外一个重要的点就是 gap lock,通常情况下我们说到 gap lock 都只会联想到 REPEATABLE-READ 隔离级别利用其解决幻读。但实际上在 READ-COMMITTED 隔离级别,也会存在 gap lock ,只发生在:唯一约束检查到有唯一冲突的时候,会加 S Next-key Lock,即对记录以及与和上一条记录之间的间隙加共享锁。

    通过下面这个例子就能验证:

    这里 session1 插入数据遇到唯一冲突,虽然报错,但是对 (15,20] 加的 S Next-Key Lock 并不会马上释放,所以 session2 被阻塞。另外一种情况就是本文开始的例子,当 session2 插入遇到唯一冲突但是因为被 X Lock 阻塞,并不会立刻报错 “Duplicate key”,但是依然要等待获取 S Next-Key Lock 。

    有个困惑很久的疑问:出现唯一冲突需要加 S Next-Key Lock 是事实,但是加锁的意义是什么?还是说是通过 S Next-Key Lock 来实现的唯一约束检查,但是这样意味着在插入没有遇到唯一冲突的时候,这个锁会立刻释放,这不符合二阶段锁原则。这点希望能与大家一起讨论得到好的解释。

    如果是在 REPEATABLE-READ,除以上所说的唯一约束冲突外,gap lock 的存在是这样的:

    普通索引(非唯一索引)的S/X Lock,都带 gap 属性,会锁住记录以及前1条记录到后1条记录的左闭右开区间,比如有[4,6,8]记录,delete 6,则会锁住[4,8)整个区间。

    对于 gap lock,相信 DBA 们的心情是一样一样的,所以我的建议是:

    1. 在绝大部分的业务场景下,都可以把 MySQL 的隔离界别设置为 READ-COMMITTED;

    2. 在业务方便控制字段值唯一的情况下,尽量减少表中唯一索引的数量。

    锁冲突矩阵

    前面我们说的 GAP LOCK 其实是锁的属性,另外我们知道 InnoDB 常规锁模式有:S 和 X,即共享锁和排他锁。锁模式和锁属性是可以随意组合的,组合之后的冲突矩阵如下,这对我们分析死锁很有帮助。

Mysql insert into select , update select 操作

参考技术A 有时候需要把查询到的数据直接插入另一个表中,sql如下:
insert into a
(a.aid, a.name)
select id, name from b
where b.xx = xx

更新操作如下:
UPDATE ta SET name = ( SELECT a.name
FROM a
WHERE ta.aid = a.id )
WHERE ta.pid = xx

内容追加:
update ta set name = CONCAT(name, 'str') WHERE id = xx //CONCAT 追加字段内容

以上是关于mysql insert into select 语句为啥会造成死锁的主要内容,如果未能解决你的问题,请参考以下文章

Mysql insert into select , update select 操作

MySQL LAST_INSERT_ID() 与 INSERT INTO tablea SELECT FROM tableb

MySQL INSERT INTO / ON DUPLICATE KEY 与 SELECT 语句问题

mysql insert into 或 select into - 将列从 tbl1 复制到 tbl2

INSERT INTO MySQL 表 SELECT FROM PostgreSQL 表

mysql insert into select 语句为啥会造成死锁