锁
一致性读
事务利用MVCC进行读取的操作叫做一致性读,或者一致性无锁读,也称为快照读。所有普通的SELECT在RC和RR下都是一致性读。不会加任何锁。
锁定读
- 共享锁:shared locks 简称s锁,在事务要读取一条记录时,首先获取该记录的s锁
- 独占锁:也称排他锁,exclusive locks,简称x锁,在事务要改动一条记录时,需要现获取该记录的X锁。
只有获取了S锁再获取S锁是可以的,而任何涉及到x锁的获取,都不能同时获取到x锁或者s锁:
兼容性 | X |
S |
---|---|---|
X |
不兼容 | 不兼容 |
S |
不兼容 | 兼容 |
对读取的记录加S锁
:
SELECT ... LOCK IN SHARE MODE;
对读取的记录加X锁
:
SELECT ... FOR UPDATE;
写操作
-
DELETE
:对一条记录做
DELETE
操作的过程其实是先在B+
树中定位到这条记录的位置,然后获取一下这条记录的X锁
,然后再执行delete mark
操作。我们也可以把这个定位待删除记录在B+
树中位置的过程看成是一个获取X锁
的锁定读
。 -
各个UPDATE
:在对一条记录做
UPDATE
操作时分为三种情况:- 如果未修改该记录的键值并且被更新的列占用的存储空间在修改前后未发生变化,则先在
B+
树中定位到这条记录的位置,然后再获取一下记录的X锁
,最后在原记录的位置进行修改操作。其实我们也可以把这个定位待修改记录在B+
树中位置的过程看成是一个获取X锁
的锁定读
。 - 如果未修改该记录的键值并且至少有一个被更新的列占用的存储空间在修改前后发生变化,则先在
B+
树中定位到这条记录的位置,然后获取一下记录的X锁
,将该记录彻底删除掉(就是把记录彻底移入垃圾链表),最后再插入一条新记录。这个定位待修改记录在B+
树中位置的过程看成是一个获取X锁
的锁定读
,新插入的记录由INSERT
操作提供的隐式锁
进行保护。 - 如果修改了该记录的键值,则相当于在原记录上做
DELETE
操作之后再来一次INSERT
操作,加锁操作就需要按照DELETE
和INSERT
的规则进行了。
- 如果未修改该记录的键值并且被更新的列占用的存储空间在修改前后未发生变化,则先在
-
INSERT
:一般情况下,新插入一条记录的操作并不加锁,设计
InnoDB
的大叔通过一种称之为隐式锁
的东东来保护这条新插入的记录在本事务提交前不被别的事务访问。
多粒度锁
表S锁:可以获取表S锁和行S锁,但是无法获取表X和行X锁
表X锁:无法获取任何维度的x和s锁。
在上表锁的时候,要确保行没有被加x锁,否则要等待,如何知道呢,通过意向锁(Intention Locks)
意向共享锁:(Intention Shared Lock)简称IS锁,当事务准备在某行记录上加S锁的时候,需要先在表上加IS锁。
意向独占锁:(Intention Exclusive Lock)
,简称IX锁
。当事务准备在某行记录上加X锁
时,需要先在表级别加一个IX锁
。
如果要给表加S锁,要看表上有没有IX锁,如果有,则等待。
如果要给表加X锁,要看表上有没有IS或者IX锁,如果有,则等待
-
IS、IX锁是表级锁,它们的提出仅仅为了在之后加表级别的S锁和X锁时可以快速判断表中的记录是否被上锁,以避免用遍历的方式来查看表中有没有上锁的记录,也就是说其实IS锁和IX锁是兼容的,IX锁和IX锁是兼容的。
兼容性 X
IX
S
IS
X
不兼容 不兼容 不兼容 不兼容 IX
不兼容 兼容 不兼容 兼容 S
不兼容 不兼容 兼容 兼容 IS
不兼容 兼容 兼容 兼容
其他存储引擎中的锁
对于MyISAM
、MEMORY
、MERGE
这些存储引擎来说,它们只支持表级锁,而且这些引擎并不支持事务,所以使用这些存储引擎的锁一般都是针对当前会话来说的。比方说在Session 1
中对一个表执行SELECT
操作,就相当于为这个表加了一个表级别的S锁
,如果在SELECT
操作未完成时,Session 2
中对这个表执行UPDATE
操作,相当于要获取表的X锁
,此操作会被阻塞,直到Session 1
中的SELECT
操作完成,释放掉表级别的S锁
后,Session 2
中对这个表执行UPDATE
操作才能继续获取X锁
,然后执行具体的更新语句。
存储引擎选择
- 如果要提供提交、回滚、崩溃恢复能力的事物安全(ACID兼容)能力,并要求实现并发控制,InnoDB是一个好的选择
- 如果数据表主要用来读,少量的写,则MyISAM引擎能提供较高的处理效率
- 如果只是临时存放数据,数据量不大,并且不需要较高的数据安全性,可以选择将数据保存在内存中的Memory引擎,MySQL中使用该引擎作为临时表,存放查询的中间结果
- 如果只有INSERT和SELECT操作,可以选择Archive,Archive支持高并发的插入操作,但是本身不是事务安全的。Archive非常适合存储归档数据,如记录日志信息可以使用Archive
使用哪一种引擎需要灵活选择,一个数据库中多个表可以使用不同引擎以满足各种性能和实际需求,使用合适的存储引擎,将会提高整个数据库的性能
InnoDB存储引擎中的锁
自增属性
-
AUTO-INC锁
:主要是给自增id用的,保证自增id的有序。插入执行完后才释放 - 采用一个轻量级的锁,(在生成完自增值后就释放)
- 也可以两种混着来,当知道要插入几条数据时用轻量,否则用auto-inc
行级锁
- Record Locks:记录锁(LOCK_REC_NOT_GAP),也就是前面讲的锁,分S和X锁。
- Gap Locks:SQL标准中,RR是会有幻读问题的,但是MySQL中解决了幻读。解决方案有两种——MVCC和锁。Gap锁是为了防止插入幻影记录(没法锁住将要加入的记录,会造成幻读)
- 如果给id为8的记录加锁,那就不允许别的事务在id为8的记录前面的间隙插入新记录,也就是(3,8)这个区间的新纪录是不允许立即插入的。比如要插入4,他会先定位到该新纪录的下一条记录的值为8,有Gap锁,就会被阻塞。
-
Next-Key Locks
:相当于Gap和普通行锁的结合 - 插入操作在等待gap锁的情况下会生成一个锁结构,也就是插入意向锁,称为Insert Intention Locks。多个插入意向锁之间不会阻塞。
- 隐式锁:一个事务中的插入操作刚入完成后,并没有任何锁,此时另外的事务可以获取这个记录的X或者S锁。会先判断隐藏列中的事务id,如果该事务id活跃,则会帮助创建一个x锁,并自己进入等待。
InnoDB锁的内存结构
- 在同一个事务中进行加锁操作
- 被加锁的记录在同一个页面中
- 加锁的类型是一样的
- 等待状态是一样的
那么这些记录的锁就可以被放到一个锁结构
中
行锁:记载了三个重要的信息:
-
Space ID
:记录所在表空间。 -
Page Number
:记录所在页号。 -
n_bits
:对于行锁来说,一条记录就对应着一个比特位,一个页面中包含很多记录,用不同的比特位来区分到底是哪一条记录加了锁。为此在行锁结构的末尾放置了一堆比特位,这个n_bits
属性代表使用了多少比特位。