【Mysql】mysql的锁
Mysql里的锁
全局锁
全局读锁: 阻止用户更新数据,但是允许用户读取数据。 全局写锁: 阻止用户读取和更新数据。 开启语句: FILUSH TABLES WITH READ LOCK, UNLOCK TABLES 一般只有迁移数据库才会使用全局锁,用的不多
表锁:
表共享读锁: 当一个事务获得了读锁,其它事务是不能获得该表的写锁,但是能再次获取读锁 表共享写锁: 当一个事务获得了写锁,其他事务是不能获得该表的读锁和写锁的
表锁使用场景:
- 读操作远远大于写操作: 即这个表完全是用于进行被查询的,因此读取数据直接获取表锁即可,效率高
- 数据量比较小: 这个时候即使修改影响其它操作效率,影响也不大
- 对全表进行更新,删除,或者更改表结构
mysql使用表锁:
- ALter table, Drop Table这些对全表进行操作的时候会锁住整个表,防止其它事务来读或写
- lock tables [tablename] write, loca tables [tablename] read
SHOW OPEN TABLES WHERE In_use >0; 查看表锁。
#查询进程 show processlist;
#杀死进程(等待锁的) kill 进程ID;
行锁:
行锁是Mysql中常用的锁定机制,对数据库表中的某一行数据进行锁定,是使用最多的。
行锁优缺点
优点: 锁粒度更小,对事务并发更友好,效率更高
缺点: 对一行进行锁定,相较于表锁,需要更大的内存和cpu资源来保存锁信息
行锁的种类
- 共享锁(S): 也叫读锁,当一个事务读取一行锁时,其它事务也可以读取这一行数据,但是不能修改
- 排它锁(X): 也叫写锁,当一行数据被上了写锁,其它事务对该数据不能读不能写。
行锁只会在事务执行期间(commit,rollback执行之前)才会生效,事务执行时会添加行锁,执行完立马释放。
行锁使用场景
- 高并发读写: 行级锁允许多个事务并发的对不同行进行操作
- 某一行进行操作,基于主键去操作单行内容
- 复杂事务,复杂事务执行期间只锁定某个行,其它事务可以为其它行进行锁定,锁定的粒度小
行锁上锁时机
- select ... for update: 添加一个排它锁(X)
- select ... lock in share mode: 加一个读锁(s)
- select: 加一个读锁(s)
- insert, update, delete: 加一个写锁(X)
行锁的问题
- 死锁: 事务A获取行A锁,请求行B锁;事务B获取行B锁,请求行A锁。死锁,超过时间回滚。
- 资源消耗: 消耗太多资源来维护每一行的数据
- 难以调试,性能出现问题,难以排查
- 如果查询没有使用到索引, 会为每一行都添加行锁,升级为表锁了
我们来举个行级锁在数据库中的使用:
- 事务A设置排它锁:
- 另开事务B去修改他们:
发现卡死了。
- 这个时候去查看锁:
可以看到事务B在等待写锁,lock_status处于wating状态, 只有事务A提交释放锁才能成功修改。
意向锁
当对行进行共享读,Mysql会为全表添加一个意向共享锁(IS)
当对行进行修改时,Mysql会为全表添加一个意向排它锁(IX)
- 在一个事务对一张表的某行添加S锁之前,它必须对该表获取一个IS锁或者优先级更高的锁,然后再添加S锁
- 在一个事务对一张表的某行添加X锁之前,它必须对该表获取一个IX锁。
为什么使用意向锁
当事务A操作表中的某个数据时,事务B来查询全表,需要对表的每一行进行判断,看是否有写锁,这样的话,效率比较低。这个时候如果有一个意向排它锁,事务B看到这个排它锁就不会再去一行行判断是否有写锁,而是直接等待锁。
意向锁可以让事务对全表进行操作的时候可以快速判断是否可以获取锁,而不用扫描每一行。
意向锁冲突情况
什么时候获取锁会发生锁冲突?
下面是官网的图:
下图里面的X与S是指对全表的操作。
- 意向锁之间是不会发生冲突的,因为意向锁是表明某个事务对某一行或者某几行进行了获取锁,而其它的行是可以再加锁的
- 对与X:任何其它锁都会冲突,所以只能wait锁释放
- 对于IX: X,S与其冲突,因为IX表示有修改,这个时候全表读,写是会被阻塞的
- 对于S: X,IX与其冲突,因为全局读的时候,肯定不能写的
- 对于IS: 与X冲突,因为IS表示有读取操作,这时候是不能全局修改的,会被阻塞
IX,IS是表级锁,不会和行级的X,S锁发生冲突。只会和表级的X,S发生冲突。
记录锁
记录锁就是为某行记录加锁,它封锁该行的索引记录:
比如使用update ... where id = 1;
注意: 这里的id必须为主键或唯一索引
- id 列必须为唯一索引列或主键列,否则上述语句加的锁就会变成临键锁。
- 同时查询语句必须为精准匹配(=),不能为 >、<、like等,否则也会退化成临键锁。
记录锁就是排它锁,是一种行锁。
间隙锁
间隙锁是封锁索引记录中的间隔,或者第一条索引记录之前的范围,又或者最后一条索引记录之后的范围。
参考:MySQL的锁机制 - 记录锁、间隙锁、临键锁 - 知乎 (zhihu.com), 这里面有具体的例子及解析。
产生间隙锁的条件(RR事务隔离级别下;):
- 使用普通索引锁定;
- 使用多列唯一索引;
- 使用唯一索引锁定多行记录。
对于使用唯一索引来搜索并给某一行记录加锁的语句,不会产生间隙锁。(这不包括搜索条件仅包括多列唯一索引的一些列的情况;在这种情况下,会产生间隙锁。)例如,如果id列具有唯一索引,则下面的语句仅对具有id值100的行使用记录锁,并不会产生间隙锁:
SELECT * FROM child WHERE id = 100 FOR UPDATE;
这条语句,就只会产生记录锁,不会产生间隙锁。
间隙锁只会阻塞insert
临键锁
临键锁是一种特殊的间隙锁,可以用于解决幻读的问题
Next-key锁是记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录前面间隙上的锁。
每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。
InnoDB中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。
去网上找的一个案例:
该表中 age 列潜在的临键锁有:
(-∞, 10], (10, 24], (24, 32], (32, 45], (45, +∞],
在事务 A 中执行如下命令:
-- 根据非唯一索引列 UPDATE 某条记录
UPDATE table SET name = Vladimir WHERE age = 24;
-- 或根据非唯一索引列 锁住某条记录
SELECT * FROM table WHERE age = 24 FOR UPDATE;
不管执行了上述 SQL 中的哪一句,之后如果在事务 B 中执行以下命令,则该命令会被阻塞:
INSERT INTO table VALUES(100, 26, 'Ezreal');
很明显,事务 A 在对 age 为 24 的列进行 UPDATE 操作的同时,也获取了(10,24], (24, 32] 这个区间(它的下一个区间)内的临键锁。
那最终我们就可以得知,在根据非唯一索引 对记录行进行 UPDATE \ FOR UPDATE \ LOCK IN SHARE MODE 操作时,InnoDB 会获取该记录行的 临键锁 ,并同时获取该记录行下一个区间的间隙锁。
即事务 A 在执行了上述的 SQL 后,最终被锁住的记录区间为 (10, 32), 如果是查询between 10 and 25,锁定的就是(10, 32]。
临键锁的使用场景
防止幻读,可以防止它的前一个范围内和它下一个区间内的间隙锁,这样这段数据范围内就不会被修改,添加,删除。
临键锁总结
临键锁是InnoDB在查询数据时锁定的一个范围,这个范围包含有间隙锁和记录锁;根据查询的条件不同、列的类型不同(是否是索引等)触发的临键锁范围也不同;
- 普通列:临键锁中的间隙锁和记录数均为表级别;
- 普通索引列:
- 非临界值:间隙锁为被查询的记录所在的区间,记录锁不生效
- 临界值:间隙锁为被查询记录所在的相邻两个区间,记录数退化为行锁
- 范围值:间隙锁和记录数均为查询条件所涉及到的区间
- 唯一索引或主键索引列:
- 非临界值:间隙锁为被查询的记录所在的区间,记录锁不生效
- 临界值:间隙锁失效,记录锁退化为行锁
- 范围值:间隙锁和记录数均为查询条件所涉及到的区间
总结
- InnoDB 中的行锁的实现依赖于索引,一旦某个加锁操作没有使用到索引,那么该锁就会为每一行加锁,退化
- 记录锁、间隙锁、临键锁,都属于排它锁;
- 记录锁就是锁住一行记录;
- 间隙锁只有在事务隔离级别 RR 中才会产生;
- 唯一索引只有锁住多条记录或者一条不存在的记录的时候,才会产生间隙锁,,指定给某条存在的记录(使用主键索引)加锁的时候,只会加记录锁,不会产生间隙锁;
- 普通索引不管是锁住单条,还是多条记录,都会产生间隙锁;
- 间隙锁会封锁该条记录相邻两个键之间的空白区域,防止其它事务在这个区域内插入、修改、删除数据,这是为了防止出现幻读现象;
- 有一个添加普通索引列的语句,mysql为其添加间隙锁,这时候另一个事务插入主键索引列与普通索引列: 产生的间隙依次排队,一个普通间隙对应一个主键间隙,优先以普通索引排序,然后再根据主键索引排序,看是否在间隙之中,在就阻塞操作;
- 事务级别是RC(读已提交)级别的话,间隙锁将会失效。
转载自:https://juejin.cn/post/7283030649610108980