1.锁的互斥性
行锁和表锁的互斥性有一定的差异,因为表锁是基于mysql自身的,而行锁是基于存储引擎的
行锁互斥性
行锁按互斥性分为共享锁和排它锁。
不同的事务可以重复获取某行数据的共享锁。但是一旦事务获取了某行数据的排它锁,其他事务就不能获取该数据的共享锁或者排它锁(因为排它锁排斥其他任何锁)。
读数据的时候默认不加锁,也可以手动加共享锁(select ****for share)或者排它锁(select ****for update)
update语句默认需要隐式的获取排它锁后才能执行
也正是因为这点,才解决了脏写的问题。
脏写:
- 两个事务对同一行数据做更新操作,A事务修改字段name,B事务修改字段age;
- A事务执行成功,B事务执行失败回滚,不光回滚了自己所做的对age字段的修改,也回滚了A事务已经成功执行并提交的对name字段的修改。
而因为update语句默认就必须加排他性的行锁,因此不可能出现多个事务同时修改同一行数据的情况,也就避免了脏写
当一行数据的排它锁被事务获取,其他的事务的读操作若没有手动加锁,则也可以成功读取到数据,反之则会阻塞直到排它锁被释放。(这一点很重要,也是行锁和表锁的的互斥性有差异的地方)
表锁互斥性
表锁分按互斥性分为读锁和写锁。当表被事务加上读锁,其他事务可以读但不能写;当表被加上写锁,则其他事务不能做任何操作(即使其他事务想不带锁的进行读写也会被阻塞,这一点和行锁不同)
2.加锁对象
行锁
主要分为记录锁(锁具体的某行数据)、间隙锁(锁左开右开区间)、临键锁(记录锁和间隙锁的结合),后两种锁只在可重复读级别以上才存在,为了避免幻读
表锁
主要分为以下几种:
-
元数据锁(锁表结构)
-
自增锁(用于事务并发插入数据时,有序获取自增id),自增锁有以下几种模式:
- 传统模式:事务获取到锁才能执行插入操作,相当于多事务串行
- 连续模式:对于插入数据条数已知的insert语句,先抢一个轻量级的锁,分配一段连续的自增id即可释放锁并执行插入,提高了并发度
- 交错模式:假设两个事务插入的数据条数不能直接从sql语句得知。采取给A事务分配1、3、5、7、9,给B事务分片2、4、6、8、10的方式,交错分配自增id
-
意向锁:加表锁时,要判断该表是否已经有数据被加了行锁,否则可能跟要加的表锁产生冲突。因此加行锁时,会给表加上一个意向锁,表示该表里有数据被加行锁了
-
普通表锁:锁住整张表的数据
3.隐式加锁时机
| 读未提交 | 读已提交 | 可重复读 | 串行化 | 不手动开启事务并开启事务自动提交 | |
|---|---|---|---|---|---|
| 读操作 | 无锁 | 无锁 | 无锁 | 共享锁 | 无锁 |
| 写操作 | 排它锁 | 排它锁 | 排它锁 | 排它锁 | 排它锁 |
上图是不同隔离级别下,隐式加锁的区别。除了串行化级别以外,其他级别都是读操作不加锁,写操作加排它锁
4.自动解锁时机
对于行锁,不管处于哪个隔离级别,都是等事务提交或回滚时才会释放锁
对于表锁,情况如下:
6.查询锁信息
查询锁信息sql
-- 查询已经存在的锁
SELECT * FROM performance_schema.data_locks;
-- 查询等待锁的事务信息
SELECT * FROM performance_schema.data_lock_waits;
截取“SELECT * FROM performance_schema.data_locks;”的部分查询列,结果如上。表示加了一个行级排它锁(rec_not_gap表示不是间隙锁,只是普通记录锁),以及一个表级意向排他锁(IX的I是intention的意思)
7.死锁检测
mysql默认有死锁检测机制,若发生死锁则会自动回滚某个事务释放锁资源