大一菜鸡的个人笔记,欢迎指点和交流。

Mysql锁机制

锁的划分

按锁的粒度划分:行锁、表锁、页锁

按锁的使用方式划分:共享锁、排它锁(悲观锁的一种实现)

按思想划分:悲观锁、乐观锁

InnoDB中的几种行级锁类型:Record Lock、Gap Lock、Next-key Lock

索引记录的锁:Record Lock

间隙锁:Gap Lock

行锁

行级锁是mysql中粒度最细的锁,表示只对当前操作的行进行加锁。行级锁能大大减小数据库操作的冲突。但粒度小同时加锁的开销也最大,而且有可能会出现死锁的情况。行级锁按照使用方式分为共享锁和排他锁

共享锁/S锁/读锁

select ... lock in share mode;

这是共享锁 也叫S锁 读锁 若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,而其他事务只能对A上S锁而不能上X锁(排他锁 也叫 写锁),除非A上的S锁被释放。这个机制保证了其他事务在释放S锁前只能读取而不能修改A。

共享锁允许多个线程同时获取一个锁,一个锁可以被多个线程拥有。

排他锁/X锁/写锁

select ... for update

这是排他锁,也叫X锁或写锁。若事务T对数据对象A加上X锁,事务T可以对A读取以及修改,其他事务不能对A加任何锁,直到T释放A上的锁,这个机制保证了其他事务在T释放A上的锁之前不能再读取和修改A。

一个锁在某一时刻只能被一个线程占有,其他线程必须等待锁被释放才能获取到锁

表锁

表级锁是mysql锁中粒度最大的一种锁,表示对整张表加锁,资源开销比行锁小,不会出现死锁的情况,但是发生锁冲突的概率很大。被大部分mysql引擎支持,MylSAM和InnoDB都支持表级锁,但是InnoDB默认的是行级锁。

共享锁用法

lock table table_name [ As alias_name ] read

排他锁用法

lock table table_name [ AS alias_name ] [ LOW_PRIORITY ] write

解锁

unlock table;

页锁

页级锁是Mysql中锁定粒度介于行级锁和表级锁之间的一种锁。表级锁速度快,但冲突多,行级锁冲突少,但速度慢。页级锁是两者折衷的做法。一次锁定相邻的一组记录。BDB支持页级锁。

乐观锁和悲观锁

数据库管理系统(DBMS)中并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。

乐观锁和悲观锁是并发控制采取的主要手段。

乐观锁和悲观锁都是一种思想,不仅仅是关系型数据库系统有乐观锁和悲观锁的概念。

悲观锁

悲观锁对数据保护持有悲观、保守的态度,依靠数据库提供的锁机制牢牢地保护数据。

悲观锁用来阻止一个事务以影响其他用户的方式来修改数据。如果应用了悲观锁,那么只有当事务释放了悲观锁,其他事务才能执行与该锁冲突的操作。悲观锁主要用于数据竞争激烈的场景,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的的成本的环境中

悲观锁机制流程

先尝试对数据加上排他锁,如果加锁失败,说明该数据正在被修改,需要进行等待或抛出异常,具体响应模式由开发者决定。如果加锁成功,那么就可以对数据进行修改,事务完成后会解锁,期间如果有其他对该数据做修改或加排他锁的操作,都会等待解锁或抛出异常。

用法

先关闭mysql中的autocommit属性

开始事务
begin; 或 begin work; 或 start transaction;
查询信息
select ... for update;
提交事务
commit; 或 commit work;

当一个窗口上了排他锁并且事务还未提交,第二个窗口如果尝试查询对应数据,会一直等待直到第一个窗口提交事务。

悲观锁的优缺点

悲观锁采取了"先取锁再访问"的策略,为数据的处理安全提供了保证,但是在效率方面,额外的加锁机制产生了额外的开销,并且增加了死锁的机会。

乐观锁

乐观机制假设各事务能在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。

乐观锁只在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突,则返回错误信息,等待用户的下一步操作。

相较于悲观锁,乐观锁并不会使用数据库提供的锁机制,一般乐观锁的实现方式就是记录数据版本。

数据版本:

为数据更新的一个版本标识,当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新,当我们提交更新时,判断数据库表对应的版本标识是否和刚开始取出的标识是否相同,不相同则证明数据已被更改,数据被认为是过期数据,否则更新版本标识,完成提交。

乐观锁的优缺点

乐观锁不会产生任何锁和死锁,但可能会出现两个事务同时读取数据,同时修改的情况。

InnoDB锁的特性

  • 在不通过索引查询时,InnoDB使用的是表锁
  • Mysql的行锁是针对索引加的锁,不是针对记录加的锁,如果使用相同索引,不同行的记录也会出现锁冲突。
  • 当表有多个索引时,不同的事务可以使用不同的索引锁定不同的行,不论是主键索引,唯一索引,普通索引,InnoDB都会使用行锁来对数据加锁。
  • 即使使用了索引字段,但是否使用索引来检索是由mysql判断的,可以使用explain查看是否真正使用了索引搜索。如果使用暴力搜索则InnoDB使用的是表锁而不是行锁。

Record Lock

Record Lock 锁的是索引,而非数据本身,如果没有索引,那么会创建一个隐藏的聚集索引。

Gap Lock

间隙锁,在索引的间隙加上锁,是Repeatable Read隔离级别下能防止幻读的主要原因。

幻读:指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。

首先num为普通索引
select * from stu where num = 3 for update; 锁住1-3
insert into stu ... (5...) 5没被锁 不受影响

防止了幻读

如果是主键索引或唯一索引,不会加上Gap锁,因为主键索引和唯一索引的值只有一个

通过范围上锁

窗口A
select * from stu where id > 3 for update

窗口B
insert into stu values(11...) 等待
insert into stu values(3...) 不等待

注意:当查询不存在的数据时,可能会把所有区间锁住

Next-Key Lock

锁住记录本身又锁住索引之间的间隙。是前面两种锁的结合。

MVCC

InnoDB存储引擎,实现的是基于多版本并发控制协议——MVCC(Multi-Version Concurrency Control)

  • 读不加锁,读写不冲突,在读多写少的OLTP应用中,读写不冲突非常重要,极大地增加了系统的并发性能。现阶段几乎所有RDBMS(关系型数据库管理系统),都支持MVCC。

2PL:Two-Phase Locking

RDBMS(关系型数据库管理系统)加锁的一个原则,就是2PL(二阶段锁)。

意思是锁操作分为两个阶段:加锁阶段和解锁阶段,并且保证加锁和解锁不相交。

死锁的原因

MylSAM不会产生死锁,因为总是能一次性获取所需的全部锁,要么全部满足,要么全部等待。而InnoDB中,锁是逐步获得的,可能会发生死锁(现在一般用InnoDB)。

在InnoDB中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引,如果一条sql语句操作了主键索引,Mysql就会锁定这条主键索引,如果一条sql语句操作了非主键索引,Mysql会先锁定该非主键索引,再锁定相关的主键索引。

当两个事务同时执行,一个锁住了主键索引,在等待其他相关索引,另一个锁住了非主键索引,在等待主键索引,就会造成死锁。

Mysql锁机制_数据

session 1 获取 id=1的锁 想要id = 5的锁

session 2 获取 id=5的锁 想要id = 1的锁

Mysql锁机制_悲观锁_02

索引的检索顺序原理是:普通索引上面保存了主键索引,当我们使用普通索引检索数据,如果所需信息不够,会继续遍历主键索引。

Session1会在name索引上的记录加锁,然后在聚簇索引上的记录加锁。

Session2顺序与Session1相反,如果两个Session恰好持有同一把锁,请求加第二把锁,死锁就发生了。

避免死锁

  • 如果并发存取表,尽量以相同的顺序访问。
  • 尽量一次锁定所需的所有资源
  • 对应易产生死锁的部分,可以提升锁粒度。表锁粒度最大。