MYSQL的锁机制

1 有哪些锁

mysql 中的锁可以按照多个维度进行分类

1.1 按照工作原理分

其实就是读写锁加上意向锁

1.1.1 共享锁(S 锁 )

持有同一个共享锁的多个进程可以同时进入保护空间,这就是共享锁命名的来源,持有通常在读取数据前加锁,以实现多个对数据的读取进程可以相互并发执行不被阻塞,因此也常被称为“读锁”

innodb 通过 MVCC 机制实现了无需加锁即可以避免读写冲突,所以在RC和RR的级别下,普通的读取是不加锁的,但 select … lock in share mode 会在行上加共享锁

1.1.2 排它锁(X 锁)

排它锁与共享锁不同,一旦加了排它锁,其他任何加锁请求都会被阻塞,排它锁通常用于写数据前加锁,以便让各个写操作之间保持互斥,因此也被成为“写锁”。

比较特殊的,select … for update 也会在行上加排它锁。

1.1.3 意向锁

意向锁就是“某些行已经加了锁”的状态标识,所有的共享锁加锁前都要对表加意向共享锁,排它锁加锁前,都要对表加意向排它锁,而意向锁之间不互斥。

意向共享锁(IS 锁):在对某一行加锁前,将整个表标记为“某些行已经加了共享锁”的状态,那么另一个事务对于整个表的加锁操作就不需要去遍历每一行了,对表级排他锁互斥。

意向排它锁(IX 锁):在对某一行加锁前,将整个表标记为“某些行已经加了排他锁”的状态,那么另一个事务对于整个表的加锁操作就不需要去遍历每一行了,对表级读写锁都互斥。

1.2 按照锁定范围分

这个就是表锁行锁和全局锁

1.2.1 全局锁

就是对整个数据库实例加锁

加锁命令

mysql 提供了一个全局锁,命令是:
1.flush tables with read lock // 加锁;
2.unlock tables // 解锁

全局锁作用效果:互斥只读

一旦全局锁命令执行成功,会关闭当前已打开的所有表,此后,该数据库实例将会变为只读,所有对数据库的 update、insert、delete、加排它锁、表结构修改等操作都会被阻塞

全局锁使用场景:实现全库备份

全局锁最常用的使用场景是全库备份

其他方式实现全库备份:快照读

只要在备份前开启一个事务,基于MVCC只进行快照读,可以保证读取到数据的一致性,官方逻辑备份工具 mysqldump 提供了参数 –single-transaction

其他方式实现全库只读:readonly

set global readonly = true,但通常我们不会去修改这个全局变量,主要原因有:
1 影响主从判断:有些系统中会根据这个变量来判断当前数据库是主库还是从库,如果擅自修改该字段,则会出现无法预期的问题。

2 解锁不及时:如果加全局锁,一旦连接断开,全局锁会自动解锁,但如果设置全局变量,发生异常时锁定并不会释放,可能产生一定的安全隐患。

1.2.2 表级锁

1.2.2.1 表锁

加锁命令是lock tables read/write,可以通过 unlock tables 命令来解锁,由于 innodb 支持行锁,而表锁锁定范围过大,通常是不被使用的。

1.2.2.2 元数据锁

元数据锁是server层的锁,表级锁(隐式添加),主要用于隔离DML和DDL操作之间的干扰,每执行一条语句时都会申请MDL锁,DML操作需要MDL读锁,DDL操作需要MDL写锁。

1.2.3 行级锁

行级锁的加锁是 Innodb 自动进行的,我们可以通过某些 SQL 语句触发相应的加锁操作,但不能自由的实现加锁和解锁的动作,如果在事务中某些行或区间被加锁,那么只有到事务结束时自动进行解锁

记录锁

记录锁就是对某行进行加锁,防止该行被其他操作修改或删除

间隙锁

间隙锁则锁的是若干个索引间的间隙,每个间隙都是两端开放的区间,其存在的目的是防止事务执行过程中另一个事务对间隙的插入,避免幻读的发生,在RC与RD隔离级别下,会被Innodb 自动禁用

临键锁

加锁的基本单位其实是临键锁(next-key lock),简单的来说,就是记录锁 + 间隙锁,也可以理解为特殊的间隙锁,他的区间是前开后闭的,开的时间隙锁,闭的就是行锁

2 临键锁加锁详情

2.1 基于索引

行锁在 InnoDB 中是基于索引实现的,通过搜索或者扫描表中索引来完成加锁操作,所以我们可以称行级锁为索引记录锁(index-record locks),一旦某个加锁操作没有使用索引,那么该锁就会退化为表锁。

2.1 加锁的隔离级别

间隙锁阻止其他事务对间隙数据的并发插入,这样可有有效的解决幻读问题,所以并不是所有事务隔离级别都需要使用间隙锁,InnoDB只有在Repeatable Read(默认)隔离级别才使用间隙锁

2.2 加锁语句

select …

InnoDB引擎采用多版本并发控制(MVCC)的方式实现了非阻塞读,所以对于普通的select读语句,InnoDB并不会加锁。

select … lock in share mode

这是一条加锁的读语句,并且锁类型为共享锁(读锁),InnoDB会加间隙锁

select … for update/update/delete

这些语句加的是排他锁(写锁),InnoDB会加间隙锁

insert…

InnoDB只会在将要插入的那一行上设置一个排他的索引记录锁。

2.3 辅助索引和排他锁

如果一个查询使用了辅助索引并且在索引记录加上了排他锁,InnoDB会在相对应的聚合索引记录上加锁

2.4 全表扫描和和记录锁

如果你的SQL语句无法使用索引,这样MySQL必须扫描整个表以处理该语句,导致的结果就是表的每一行都会被锁定(记录锁),并且阻止其他用户对该表的所有插入。

2.5 加锁场景验证

判断两条SQL语句是否相互锁定时,需要注意的是,对于索引的查询条件,不能想当然的理解,需要结合执行计划判断索引最终扫描的记录数,否则会对加锁范围理解产生偏差。

判断加锁类型时,因为间隙锁只会阻止insert语句,所以同样的索引数据,insert语句阻塞而select for update语句不阻塞的就是间隙锁,如果两条语句都阻塞就是索引记录锁。

注意,当存在普通索引跟唯一索引混合的数据间隙时,数据行是优先根据普通索引排序,再根据唯一索引排序,比如(‘1’, ‘a’),(‘2’,‘a’),(‘3’,‘b’),(‘4’,‘b’)。以下分类说明不同加锁场景下的加锁情况

2.5.1 辅助索引等值查询

INSERT INTO user (id, name) VALUES (‘1’, ‘a’),(‘3’, ‘c’),(‘5’, ‘e’),(‘7’, ‘g’),(‘9’, ‘i’)(其中name为普通索引),SELECT * FROM user where name=‘e’ for update

辅助索引记录行加临键锁:对SQL语句扫描过的辅助索引记录行加上next-key锁(注意也锁住记录行之后的间隙),加锁范围为(c,e],(e,g)。

聚合索引加上索引记录锁:对辅助索引对应的聚合索引加上索引记录锁,即对id=5的聚合索引记录上添加了索引记录锁,会阻塞语句SELECT * FROM user where id=5 for update等排他锁语句。

边界内聚合索引加间隙锁:当辅助索引为间隙锁边界值时,对聚合索引相应的行加间隙锁,“最小”锁定对应聚合索引之后的,“最大”值锁定对应聚合索引之前的。即(‘3’, ‘c’)之后和(‘7’, ‘g’)之前阻塞,如(‘4’, ‘c’),(‘6’, ‘g’)。

2.5.2 聚合索引和唯一索引等值查询

INSERT INTO user (id, name) VALUES (‘1’, ‘a’),(‘3’, ‘c’),(‘5’, ‘e’),(‘7’, ‘g’),(‘9’, ‘i’)(其中name为唯一索引),SELECT * FROM user where name=‘e’ for update

只加索引记录锁:对于唯一索引列加锁,记录存在时,间隙锁失效,即只有name='e’这行数据被锁定

2.5.3 辅助索引范围查询

INSERT INTO user (id, name,age) VALUES (‘1’, ‘a’,‘15’),(‘3’, ‘c’,‘20’),(‘5’, ‘e’,‘16’),(‘7’, ‘g’,‘19’),(‘9’, ‘i’,‘34’)(其中name为普通索引),select * from user where name>‘e’ for update

语句扫描了两行索引记录分别是g和i,所以我们将g和i的锁定范围叠加就可以得到where name>'e’的锁定范围。

**辅助索引记录行加临键锁:**索引记录g在name列锁定范围为(e,g],(g,i)。索引记录i的在name列锁定范围为(g,i],(i,+∞)。两者叠加后锁定范围为(e,g],(g,i],(i,+∞)。其中g,i为索引记录锁。

聚合索引加上索引记录锁:g和i对应id列中的7和9加索引记录锁。

**边界内聚合索引加间隙锁:**当name列的值为锁定范围上边界e时,还会在e所对应的id列值为5之后的所有值之间加上间隙锁,范围为(5,7),(7,9),(9,+∞)。下边界为+∞无需考虑。

2.5.4 唯一索引的范围查询

INSERT INTO user (id, name,age) VALUES (‘1’, ‘a’,‘15’),(‘3’, ‘c’,‘20’),(‘5’, ‘e’,‘16’),(‘7’, ‘g’,‘19’),(‘9’, ‘i’,‘34’)(其中name为唯一索引),select * from user where name>‘e’ for update

边界不会对主键加锁:和普通索引的范围查询类似,唯一不同的是当辅助索引等于上下范围的边界值时不会对主键加上间隙锁

2.6 串行化和普通select间隙锁

在事务隔离级别为SERIALIZABLE时,普通的select语句也会对语句执行过程中扫描过的索引加上next-key锁。如果语句扫描的是唯一索引,那就将next-key锁降级为索引记录锁了。

你可能感兴趣的:(笔记,SQL,数据库)