mysql中insert会加锁吗
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql中insert会加锁吗相关的知识,希望对你有一定的参考价值。
加锁情况与死锁原因分析
为方便大家复现,完整表结构和数据如下:
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的start transcation会加锁吗
目标
确认MySQL transaction开启时是否会加锁
Select..for Update的锁与释放机制
MySQL transcation与锁的关系
In the InnoDB transaction model, the goal is to combine the best properties of a multi-versioning database with traditional two-phase locking. InnoDB performs locking at the row level and runs queries as nonlocking consistent reads by default, in the style of Oracle. The lock information in InnoDB is stored space-efficiently so that lock escalation is not needed. Typically, several users are permitted to lock every row in InnoDB tables, or any random subset of the rows, without causing InnoDB memory exhaustion.
Innodb的事务模型目标是将数据库多版本和两阶段锁的最好特点结合起来。InnoDB在执行事务时,像oracle一样,默认采用行级锁和可重复读。在InnoDB中,锁信息是很小的,所以对于存储方面没有很大的需求,就算在比较极端的情况下,多个用户锁定了InnoDB的每一行,或者随机多行的锁定,也不会引起内存溢出。
InnoDB offers all four transaction isolation levels described by the SQL:1992 standard: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, and SERIALIZABLE. The default isolation level for InnoDB is REPEATABLE READ.
InnoDB提供SQL-1992标准的四个事务隔离级别: READ UNCOMMITED, READ COMMITED, REPEATABLE READ, SERIALIZABLE. InnoDB默认级别是REPEATABLE READ。
InnoDB supports each of the transaction isolation levels described here using different locking strategies. InnoDB为不同的隔离级别,提供了不同的锁策略。
Repeatable Read. This is the default isolation level for InnoDB. Consistent reads within the same transaction read the snapshot established by the first read. This means that if you issue several plain (nonlocking) SELECT statements within the same transaction, these SELECT statements are consistent also with respect to each other
可重复读这是InnoDB默认的隔离级别,通过快照的方式实现了可重复读。这意味着如果使用的是非锁读请求在一个事务里,读取到的数据总是一致的。
For locking reads (SELECT with FOR UPDATE or LOCK IN SHARE MODE), UPDATE, and DELETE statements, locking depends on whether the statement uses a unique index with a unique search condition, or a range-type search condition. For a unique index with a unique search condition, InnoDB locks only the index record found, not the gap before it. For other search conditions, InnoDB locks the index range scanned, using gap locks or next-key locks to block insertions by other sessions into the gaps covered by the range.
对于像 SELECT..FOR UPDATE
或者 LOCK IN SHARE MODE
等这种锁读请求的方式、UPDATE和DELETE请求来说,锁决定于是否语句可以使用一个唯一索引,并且有一个唯一的搜索条件或者范围搜索条件。
对于唯一索引并且使用了唯一搜索条件, InnoDB会锁定找到的行,不会加gap lock.
对于其他搜索条件时, InnoDB会使用gap lock或者next-key lock(阻塞其他会话插入).
Beginning a transaction (for example, with START TRANSACTION) implicitly commits any current transaction and releases existing table locks 开始一个事务时,会默认提交当前的事务,并且释放表锁。
从上述文档来看,InnoDB开启事务时并不会有表锁,根据要更新的语句会有行级锁。但事务和表锁之间有互相关联的关系,比如在开启事务时会默认提交当前会话中已经存在的事务,并且释放表锁。
验证:
情况一: A开启事务并查询,B进行drop
Session A:
create table test (id bigint(11));
insert into test (id) values (1);
start transaction;
Session B:
insert into test(id) values(2);
Session A:
select * from test;
Session B:
drop table test;
执行结果: A的select看不到事务开启后的id=2数据,B的drop因为select的原因被阻塞。
情况二: A开启事务,B进行drop, A进行查询
Session A:
start transaction;
Session B:
drop table test;
Session A:
select * from test;
B的drop不会阻塞,A的查询会返回表不存在的错误。
从上述验证结果可以得到以下结果:
开启事务并不会对表进行加锁
事务中的语句会对表或者记录进行加锁
一个表可以并发执行多个事务
相关资料
InnoDB Transaction Model
Interaction of Table Locking and Transactions
Select..for Update的锁与释放机制
A SELECT ... FOR UPDATE reads the latest available data, setting exclusive locks on each row it reads. Thus, it sets the same locks a searched SQL UPDATE would set on the rows.
对于SELECT ... FRO UPDATE会读取最新的数据,并且设置排他锁在需要的每一行记录上,就像Update一样。
If you query data and then insert or update related data within the same transaction, the regular SELECT statement does not give enough protection. Other transactions can update or delete the same rows you just queried
如果在一个事务里查询并且对数据进行操作,普通的select语句并不能提供有效的保护。其他事务可以在这个过程中对这些行更新或者删除。
SELECT ... FOR UPDATE sets an exclusive next-key lock on every record the search encounters. However, only an index record lock is required for statements that lock rows using a unique index to search for a unique row.
SELECT ... FOR UPDATE会在每一个要锁定的记录设置一个排他next-key排他锁。这个是需要一个唯一索引来搜索满足条件的唯一行,找到之后在索引记录上加锁。
To avoid deadlocks when performing multiple concurrent write operations on a single InnoDB table, acquire necessary locks at the start of the transaction by issuing a SELECT ... FOR UPDATE statement for each group of rows expected to be modified, even if the data change statements come later in the transaction. If transactions modify or lock more than one table, issue the applicable statements in the same order within each transaction.
为了避免多个并非写InnoDB产生思索,可以在事务开始时用 SELECT...FOR UPDATE
的方式锁定要修改的每一行。如果事务修改或者锁定涉及多个表,建议采用同样的顺序来进行锁定。
以上是关于mysql中insert会加锁吗的主要内容,如果未能解决你的问题,请参考以下文章