一、锁分类

  1. 从性能上讲:有悲观锁和乐观锁。悲观锁适合写操作比较多的场景,乐观锁适合读操作比较多的场景;
  2. 从对数据操作的粒度讲:有表锁、行锁、页锁;
  3. 从对数据库操作的类型讲:读锁(悲观锁)、写锁(悲观锁)、意向锁。

二、锁定义

悲观锁

悲观认为一定会有其他的线程改变数据,所以其他线程想要修改数据必须等待前面的线程执行完毕后解锁才能执行后续流程,保证了数据的一致性,但浪费性能。

乐观锁

乐观认为自己在操作的时候不会有其他线程来修改数据,可以认为不加锁,采用CAS机制,根据MySQL底层维护的版本号,来保证数据的最终一致性。

表锁

每次锁住整张表,开销小,加锁快;不会出现死锁;粒度大,发生锁冲突概率高;并发低,适合整表的数据迁移。

页锁

只有BDB存储引擎支持页锁,页锁就是在页的粒度上进行锁定,锁定的数据资源比行锁要多,因为一个页中可以有多个行记录。当我们使用页锁的时候,会出现数据浪费的现象,但这样的浪费最多也就是一个页上的数据行。页锁的开销介于表锁和行锁之间,会出现死锁。锁定粒度介于表锁和行锁之间,并发度一般。

行锁

锁粒度最小;每次锁住一行数据;开销大,加锁慢,容易出现死锁;并发度高。

1、InnoDB和myisam引擎最大的不同就是:

  • InnoDB支持事务;
  • InnoDB支持行锁。

tips:行锁对应的是该行所采用的索引,若修改数据时,条件为非索引字段,行锁会升级为表锁。(隔离级别为RR(可重复读)的会自动升级,RC(读已提交)的不会升级为表锁)。

2、RR(可重复读)级别下为什么行锁会自动升级为表锁?

在RR(可重复读)级别下,需要解决幻读和不可重复读问题,所以在遍历索引记录时,防止读取记录数据被其他事务修改(不可重复读),或间隙被其他事务插入记录(幻读),导致的数据不一致,所以MySQL会将扫描过的行以及间隙都锁上。但是不一定直接加表锁,因为表中数据有可能会被其他事务锁住。

间隙锁

锁住两个值之间的空隙(不包括两边的值),间隙锁是只有在RR(可重复读)级别下才会生效,解决扫描过的索引数据被插入数据(幻读)问题。

临键锁

行锁+间隙锁,间隙锁两边的值只要有一个值加了行锁就是临键锁。

读锁(共享锁)

对同一数据,多个读操作可以同时进行而不会互相影响。

写锁(排它锁)

当前写操作没有完成时,会阻塞读操作和写操作。

总结:

myisam在select时,会自动给涉及的表加读锁,在update、delete、insert操作时自动给涉及的表加写锁(都是表锁);

InnoDB在select时(非串行级别)不会加锁,在update、delete、insert操作时会加行锁;

读锁会阻塞写操作,不会阻塞读操作;写锁会阻塞读操作和写操作。

意向锁

针对表锁,意向锁相当于一个标识,当A事务对表中数据添加行锁后,B事务需要添加表锁就不需要逐行判断是否背加了其他锁,直接看是否有意向锁这个标识就可以。

三、锁等待分析

1.通过InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况

show status like 'innodb_row_lock%';


对于这5个状态变量,比较重要的主要是:
Innodb_row_lock_time_avg (等待平均时长)
Innodb_row_lock_waits (等待总次数)
Innodb_row_lock_time(等待总时长)

MySQL锁机制及优化_锁分类

尤其是等待次数很高,而且每次等待时长也不小的时候,需要分析系统中为什么需要这么多等待?根据分析结果着手优化。

2.查看INFORMATION_SCHEMA系统库锁相关数据表

在RR级别下,手动给行加锁,通过InnoDB_row_lock状态变量来分析变量:

MySQL锁机制及优化_锁分类_02

  • 查看当前正在执行的事务
select * from INFORMATION_SCHEMA.INNODB_TRX;

我们可以看到事务id,事务运行状态,锁等待id、运行语句、隔离级别等信息。

MySQL锁机制及优化_死锁_03

  • 查看系统中正在加的锁
select * from INFORMATION_SCHEMA.INNODB_LOCKS;
 -- 8.0之后需要换成这张表performance_schema.data_locks

我们可以看到锁id、事务id、锁类型(排它锁)等信息

MySQL锁机制及优化_锁分析_04


  • 查看多少锁正在等待详细信息
select * from INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

-- 8.0之后需要换成这张表performance_schema.data_lock_waits

MySQL锁机制及优化_死锁_05


  • 释放锁
kill trx_mysql_thread_id
-- trx_mysql_thread_id可以从INNODB_TRX表里查看到

大多数情况下,MySQL会自动检测死锁并回滚事务,有些情况下没法自动检测死锁,可以直接通过日志分析找的导致死锁事务的线程id,通过kill 释放掉。

四、锁优化实践

  1. 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁;
  2. 合理设计索引,尽量缩小锁的范围;
  3. 尽可能减少检索条件范围,避免间隙锁;
  4. 尽量控制事务大小,减少锁定资源量和时间长度,涉及事务加锁的sql尽量放在事务最后执行;
  5. 尽可能用低的事务隔离级别。