MySQL ---- 共享锁独占锁行锁表锁
Posted whc__
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL ---- 共享锁独占锁行锁表锁相关的知识,希望对你有一定的参考价值。
锁
锁机制,解决的是多个事务同时更新一行数据,此时必须要有一个加锁的机制。
依靠锁机制,让多个事务更新一行数据的时候串行化,避免同时更新一行数据。
当一个事务想对记录进行修改时,首先会看看内存中有没有与这条记录关联的锁结构
,当没有的时候会在内存中生成一个锁结构
与之关联。
更新一行数据时必须把它所在的数据页从磁盘文件里读取到缓存页里面才能更新,此时数据和关联的锁数据结构都是在内存里的。
比如说事务T1要对记录做改动时,就需要生成一个锁结构
与之关联:
- trx信息:表示这个锁结构是哪个事务生成的
- is_waiting:代表当前事务是否在等待
如果事务2也想对记录做改动时,首先看看有没有锁结构
与这条记录关联,发现有一个锁结构
与之关联后,然后也生成了一个锁结构
与这条记录关联。不过锁结构
的is_waiting
属性值为true,表示当前事务需要等待。
事务T1提交之后,就会把该事务生成的锁结构
释放掉,然后看看还有没有别的事务在等待获取锁,发现了事务T2还在等待获取锁,所以把事务T2对应的锁结构的is_waiting
属性设置为false,然后把该事务对应的线程唤醒,让它继续执行。
1、一致性读(Consitent Reads)
事务利用MVCC
进行的读取操作称为一致性读
,或者一致性无锁读
,也称为快照读
。
所有普通的SELECT语句在RC、RR隔离级别下都算是一致性读,比方说:
SELECT * FROM t;
SELECT * FROM t1 INNER JOIN t2 ON t1.col1 = t2.col2;
一致性读并不会对表中的任何记录加锁操作,其它事务可以自由的对表中的记录做改动
2、锁定读(Locking Reads)
2.1 共享锁
共享锁:简称S锁
语法:select * from table lock in share mode
查询的时候对一行数据加共享锁,如果此时别的事务在更新这行数据已经加了独占锁,那么共享锁是不能加的,因为共享锁和独占锁是互斥的,此时查询只能等待。
锁类型 | 独占锁 | 共享锁 |
---|---|---|
独占锁 | 互斥 | 互斥 |
共享锁 | 互斥 | 不互斥 |
(一般开发业务系统的时候,其实查询主动加共享锁比较少见,一般不会在数据库层面做复杂的手动加锁操作,反而会用基于redis/zookeeper
的分布式锁来控制业务系统的锁逻辑)
2.2 独占锁
独占锁:也常称排他锁,简称X锁
当有一个事务加了独占锁之后,此时其它事务再要更新这行数据,都是要加独占锁的,但是只能生成独占锁在后面等待。
如果其它事务想要读取这行数据,默认情况下,是不会加锁的,因为可以通过MVCC机制
解决读写互斥问题,避免频繁加锁互斥。
如果真的要对读取行记录加锁,查询操作也能加独占锁,语法:select * from table for update
。
2.2 锁定读语句
-
对读取的记录加S锁:
SELECT … LOCK IN SHARE MODE
-
对读取的记录加X锁:
SELECT … FOR UPDATE
3、行锁
对于MyISAM、MEMORY存储引擎来说,它们只支持表级锁,而且这些引擎并不支持事务,所以使用这些存储引擎的锁一般都是针对当前会话来说的。
而InnoDB存储引擎即支持表锁,也支持行锁。
- 表锁:实现简单,占用资源较少,粒度很粗
- 行锁:实现复杂,占用资源较多,粒度很细
多个事务并发运行更新一条数据,默认加独占锁互斥,同时其它事务读取基于MVCC机制进行快照读,实现事务隔离。
3.1 行锁
行锁都是针对记录的,也可以称之为行级锁
或者行锁
,锁的粒度比较细
3.1.1 Record Locks(记录锁)
-
Record Locks(记录锁):仅对一条记录加锁
-
S型记录锁
一个事务获取到了一条记录的S型记录锁后,其它事务也可以继续获取该记录的S型记录锁,但是不能继续获取该记录的X型记录锁
-
X型记录锁
一个事务获取到了一条记录的X型记录锁后,其它事务既不可以获取该记录的S型记录锁,也不能继续获取该记录的X型记录锁
-
3.1.2 Gap Locks
Gap Locks:简称gap锁,RR隔离级别下可以解决幻读问题,解决方案有两种,一种是快照读时用MVCC解决,另一种是当前读(比如select * from table for update)时MVCC + Next-Key Lock。
如图对number值为8的记录加了gap锁,意味着不允许别的事务在number值为8的记录前边的间隙插入新记录,即number列的值(3,8)这个区间不允许立即插入。
比如插入一条number值为4的新记录,它定位到该条新记录的下一条记录的number值为8,而这条记录上又有一个gap锁,所以就被阻塞插入操作,直到拥有gap锁的事务提交了之后,新记录才可以被插入。
3.1.3 Next-Key Lock
Next-Key Lock:本质上是记录锁和gap锁的结合
,既锁住了某条记录,又阻止了其它事务在该记录前边的间隙插入新记录
3.1.4 Insert Intention Locks
Insert Intention Locks(插入意向锁):一个事务在插入一条记录时需要判断一下插入位置是不是被别的事务加了所谓的gap锁,如果有的化,插入操作需要等待,直到拥有gap锁的那个事务提交,等待的时候在内存中生成一个锁结构,表明有事务想在某个间隙中插入新记录。
3.1.5 隐式锁
一个事务在执行INSERT操作时,如果即将插入的间隙已经被其它事务加了gap锁
,那么本次INSERT
操作会阻塞,并且当前事务会在该间隙上加一个插入意向锁
,否则一般情况下INSERT
操作是不加锁的。
那如果一个事务首先插入了一条记录(此时并没有与该记录关联的结构),然后另一个事务:
-
立即使用SELECT … LOCK IN SHARE MODE语句读取这条事务,即获取这条记录的S锁,或者使用SELECT … FOR UPDATE语句读取这条事务或者直接修改这条记录,也就是获取这条记录的X锁,该咋办?
(如果允许发生,则可能产生脏读问题)
-
立即修改这条记录,即获取这条记录的X锁
(如果允许发生,则可能产生脏写问题)
所以对于上面的情况,就需要借助行记录中的事务id
了,对于聚簇索引记录来说,它有一个trx_id
隐藏列,记录着最后改动该记录的事务id,当新插入一条记录索引记录后,trx_id
代表当前事务的事务id
。如果其它事务此时想对该记录添加S锁或X锁时,如果发现该记录的trx_id
隐藏列代表的事务是当前的活跃事务,那么就帮当前事务创建一个X锁,然后自己进入等待状态。
所以可以知道,一个事务对新插入的记录可以不显示的加锁(生成一个锁结构),但是由于事务id
的存在,相当于加了一个隐式锁
。别的事务在对这条记录加S锁或者X锁时,由于隐式锁
的存在,会先帮助当前事务生成一个锁结构,然后自己再生成一个锁结构后进入等待状态。
3.2 表锁
一个事务在表级别进行加锁,称之为表级锁
或者表锁
,锁的粒度比较粗
表锁可以分为:表锁的S、X锁;表锁意向锁的IS、IX锁;表级别的AUTO-INC
锁
3.2.1 表锁的S、X锁
- LOCK TABLES xxx READ:加表级共享锁
- LOCK TABLES xxx WRITE:加表级独占锁
3.2.2 表锁意向锁的IS、IX锁
- 意向共享锁(IS):如果有事务在表里执行查询操作,会在表级加一个意向共享锁
- 意向独占锁(IX):如果有事务在表里执行增删改操作,会在行级加独占锁,同时也会表级加一个意向独占锁
锁类型 | 表级独占锁 | 意向独占锁 | 表级共享锁 | 意向共享锁 |
---|---|---|---|---|
独占锁 | 互斥 | 互斥 | 互斥 | 互斥 |
意向独占锁 | 互斥 | 不互斥 | 互斥 | 不互斥 |
共享锁 | 互斥 | 互斥 | 不互斥 | 不互斥 |
意向共享锁 | 互斥 | 不互斥 | 不互斥 | 不互斥 |
IS、IX锁是表级锁,它们的提出仅仅为了在之后加表级别的S锁和X锁时可以快速判断表中的记录是否被上锁,以避免用遍历的方式来查看表中有没有上锁的记录,也就是说其实IS锁和IX锁是不互斥的,IX锁和IX锁不互斥、IS锁和IS锁不互斥的。
(共享锁和独占锁是互斥的、共享锁和共享锁是不互斥的)
3.2.3 表级别的AUTO-INC锁
在为表的某个列添加AUTO_INCREMENT
属性,之后在插入记录时,可以不指定该列的值,系统会自动为它赋上递增的值。
赋值的原理原理之一就是采用AUTO-INC
锁。在执行插入语句时在表级别加一个AUTO-INC锁,然后为每条待插入记录的AUTO_INCREMENT修饰的列分配递增的值,在该语句执行结束后,再把AUTO-INC锁释放掉。这样一个事务在持有AUTO-INC锁的过程中,其它事务的插入语句都要被阻塞,可以保证一个语句中分配的递增值是连续的。
AUTO-INC锁的作用范围只是单个插入语句,插入语句执行完成后,这个锁就被释放了。
以上是关于MySQL ---- 共享锁独占锁行锁表锁的主要内容,如果未能解决你的问题,请参考以下文章