MySQL锁

MySQL锁分类

每次在听别人说锁的时候,是不是会有点儿晕?(一会儿排它锁,一会儿GAP锁...)因为你站在不同的角度来说,它的名字就会不同。根据我们DB的引擎、隔离级别不同,导致的锁的情况也会不同

下面根据几种不同的类型对锁做一个划分:

力度划分:

  • 表级锁:表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定,开销小,加锁快,粒度大,锁冲突概率大,并发度低,适用于读多写少的情况。
  • 页级锁:页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁。

  • 行级锁:行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。Innodb存储引擎,默认选项。

模式划分:

  • 记录锁:其实很好理解,对表中的记录加锁,叫做记录锁,简称行锁。
  • GAP锁:只在RR和Serializable级别下生效.通过gap锁防止其他事务在一定区间插入、删除、修改,来避免幻行问题。
  • Next-key锁:是 MySQL 的 InnoDB 存储引擎的一种锁实现,MVCC 不能解决幻读的问题,Next-Key Locks 就是为了解决这个问题而存在的。
  • 意向锁:意向锁是一种不与行级锁冲突表级锁,这一点非常重要。意向锁分为两种意向共享锁(intention shared lock, IS)与意向排他锁(intention exclusive lock, IX)。
  • 插入意向锁普通的Gap Lock 不允许 在 (上一条记录,本记录) 范围内插入数据,插入意向锁Gap Lock 允许 在 (上一条记录,本记录) 范围内插入数据。

机制划分:

  • 悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
  • 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。

兼容性划分:

  • 共享锁:多个事务只能读数据不能改数据。
  • 排它锁:又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

http://static.cyblogs.com/WX20191219-140410@2x.png

隔离级别

在数据库操作中,为了有效保证并发读取数据的正确性,提出的事务隔离级别。我们的数据库锁,也是为了构建这些隔离级别存在的。

| 隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) | | ---------------------------- | ---------------------- | ------------------------------------ | ------------------------ | | 未提交读(Read uncommitted) | 可能 | 可能 | 可能 | | 已提交读(Read committed) | 不可能 | 可能 | 可能 | | 可重复读(Repeatable read) | 不可能 | 不可能 | 可能 | | 可串行化(Serializable ) | 不可能 | 不可能 | 不可能 |

  • 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据。
  • 提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)。
  • 可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读。
  • 串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。

如何查看一个数据库的隔离级别呢?

1、查看当前会话  
mysql> select @@tx_isolation;  
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.00 sec)  
这是我本地的mysql数据库,也就是说默认的级别就是:REPEATABLE-READ

2、查看系统当前隔离级别  
mysql> select @@global.tx_isolation;  
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ       |
+-----------------------+
1 row in set (0.00 sec)  

查看数据库死锁日志

// innodb_locks记录了所有innodb正在等待的锁,和被等待的锁
select * from information_schema.innodb_locks;

// innodb_lock_waits记录了所有innodb锁的持有和等待关系
select * from information_schema.innodb_lock_waits;

show engine innodb status \G  

说明:通过show engine innodb status 查看的日志是最新一次记录死锁的日志,但是查看不到完整的事务的sql,通常显示当前正在等待锁的sql;

// 表示事务4641对表`sys`.`new_table`持有了IX锁
TABLE LOCK table `sys`.`new_table` trx id 4641 lock mode IX  
// space id=38,space id可以唯一确定一张表,表示了锁所在的表
// page no 3,表示锁所在的页号
// index PRIMARY 表示锁位于名为PRIMARY的索引上
// lock_mode X locks rec but not gap 表示x record lock
// 下方的数据表示了被锁定的索引数据,最上面一行代表索引列的十六进制值,在这里表示的就是id=3的数据
RECORD LOCKS space id 38 page no 3 n bits 80 index PRIMARY of table `sys`.`new_table` trx id 4641 lock_mode X locks rec but not gap  
Record lock, heap no 4 PHYSICAL RECORD: n_fields 8; compact format; info bits 0  
 0: len 4; hex 00000003; asc     ;;
 1: len 6; hex 0000000011e9; asc       ;;
 2: len 7; hex a70000011b0128; asc       (;;
 3: len 4; hex 8000012c; asc    ,;;
 4: len 1; hex 63; asc c;;
 5: len 4; hex 80000006; asc     ;;
 6: len 3; hex 636363; asc ccc;;
 7: len 2; hex 3333; asc 33;;
// lock_mode X表示的是next-key lock,即当前记录的record lock+前一个间隙的gap lock
// 这个锁在名为idx1的索引上,对应的索引列的值为100(hex 64对应十进制),对应聚簇索引的值为1
RECORD LOCKS space id 38 page no 5 n bits 80 index idx1 of table `sys`.`new_table` trx id 4643 lock_mode X  
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0  
 0: len 4; hex 00000064; asc    d;;
 1: len 4; hex 00000001; asc     ;;

// lock_mode X locks gap before rec表示的是对应索引记录前一个间隙的gap lock
RECORD LOCKS space id 38 page no 5 n bits 80 index idx1 of table `sys`.`new_table` trx id 4643 lock_mode X locks gap before rec  
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0  
 0: len 4; hex 800000c8; asc     ;;
 1: len 4; hex 00000002; asc     ;;

死锁日志解析:

  • lock_mode X locks rec but not gap:模式排它锁,类型行锁;

  • lock_mode X locks gap before rec :模式排它锁,类型gap锁;

  • lock_mode X locks gap before rec insert intention:模式排它锁,类型插入意向锁;

  • lock_mode X:Next-key锁;

总结:

  • 索引记录的间隙上用来避免幻读。

  • Select(Serializable隔离级别除外)不会加锁,而是执行快照读。

  • 写操作都会加锁,具体加锁方式取决于隔离级别、索引命中情况以及修改的索引情况。

  • 为了减少锁的范围,避免死锁的发生,应该尽量让查询条件命中索引,而且命中的越精确加锁越少。同时如果能接受RC级别对一致性的破坏,可以将隔离级别调整成RC。

参考地址

简栈文化服务订阅号