事务第01讲:数据库锁(MySQL)

事务第01讲:数据库锁[MySQL]

  • 1. 为什么需要锁
  • 2. MySQL锁
    • 2.1 锁粒度
    • 2.2 共享锁(share锁)
      • 2.2.1 概念
      • 2.2.2 栗子
    • 2.3 排它锁(eXclusive锁)
      • 2.3.1 概念
      • 2.3.2 栗子
    • 2.5 小结
  • 3. 抽象锁
    • 3.1 悲观锁
      • 2.6.1 概念
      • 2.6.2 悲观锁的实现
    • 2.7 乐观锁
      • 2.7.1 概念
      • 2.7.2 乐观锁实现
  • 5. InnoDB-MVCC与乐观锁
  • 6. 死锁
  • 99.参考资料

部分资料引用自各路大神,具体见“99.参考资料”, 感谢各路大神的鼎力支持!!!

1. 为什么需要锁

并发性问题:在多用户环境下,在同一时间可能会有多个用户更新相同的记录,这会产生冲突。
典型冲突:脏读、不可重复读、幻读等。
解决方法:我们来瞧一瞧并发控制机制(即,锁)。

2. MySQL锁

给我们选定的目标数据上锁,使其无法被其他程序修改
并发控制:是指在多个用户/进程/线程同时对数据库进行操作时,如何保证事务的一致性和隔离性的同时最大程度的并发

2.1 锁粒度

  • 表锁:给整张表加锁。开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低
  • 行锁:给某一行加上锁。开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高
  • 页锁:开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般

PS:MySQL的锁机制就是通过存储引擎支持不同的锁机制。

行锁 表锁
MyISAM
InnoDB

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

InnoDB 行锁是通过给索引项加锁实现的,如果没有索引,InnoDB 会通过隐藏的聚簇索引来对记录加锁。也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样。因为没有了索引,找到某一条记录就得扫描全表,要扫描全表,就得锁定表。

【以下均以InnoDB存储引擎举栗说明】

2.2 共享锁(share锁)

2.2.1 概念

共享锁:S锁,又被称作读锁,共享锁的锁粒度是行或元组(多个行)。
一个事务获取了共享锁之后,可以对锁定的范围内的数据执行读操作。假设事务T对数据对象A加上了S锁,则事务T可以读A但不能修改A,其他事物只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事物可以读A,但是在事务T释放A上的S锁之前不能对A做任何操作
共享锁就是允许多个线程同时获取一个锁,一个锁可以同时被多个线程拥有。

2.2.2 栗子

【注意】:验证时,要关闭MySQL的自动提交(set autocommit = 0),使用 select * from table_name where ... lock in share mode; 显示的加S锁。

1). 打开一个【客户端A和客户端B】,查询表数据
事务第01讲:数据库锁(MySQL)_第1张图片
2). 【客户端A】对数据加S锁
事务第01讲:数据库锁(MySQL)_第2张图片
3). 【客户端A】对 u_id = ‘0’ 的数据加了S锁,此时,我客户端B也可以对该记录加S锁。同上图。

4). 【客户端A】对锁定的记录进行更新操作,可以看出在等待锁
在这里插入图片描述2.2.2.4
5). 此时,【客户端B】也对该条记录进行更新操作,则会出现死锁,然后退出
在这里插入图片描述2.2.2.5
6). 当【客户端B】退出后,【客户端A】立即会获得锁,更新成功。
在这里插入图片描述

2.3 排它锁(eXclusive锁)

2.3.1 概念

排它锁:X锁,又被称作写锁,排它锁的锁粒度与共享锁一样。
一个事务获取了排它锁之后,可以对锁定范围内的数据执行写操作。假设事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事物不能再对A加任何锁,直到事务T释放A上的锁。这保证了其他事物在事务T释放A上的锁之前不能再读和修改A
排它锁,也称作独占锁,一个锁在某一时刻只能被一个线程占有,其他线程必须等待锁被释放之后才能获取到锁。

2.3.2 栗子

【注意】:验证时,要关闭MySQL的自动提交(set autocommit = 0),使用 select * from table_name where ... for update; 显示的加X锁。

1). 【客户端A】对数据加X锁
事务第01讲:数据库锁(MySQL)_第3张图片
2). 【客户端B】只能查询该数据,如果再对该数据加X锁,则就处于锁等待状态
事务第01讲:数据库锁(MySQL)_第4张图片
3). 【客户端A】对锁定记录执行更新操作后,显示提交(commit),提交后就会释放锁
事务第01讲:数据库锁(MySQL)_第5张图片
4). 当【客户端A】释放锁后,【客户端B】在第2步执行的for update就会立即执行成功。
事务第01讲:数据库锁(MySQL)_第6张图片

2.5 小结

  • 共享锁(S):允许一个事务去读一行,阻止其他事物获得相同数据集的排它锁;
  • 排它锁(X):允许获得排它锁的事务更新数据,阻止其他事物取得数据集的共享锁和排它锁。
  • 意向共享锁(IS):事务打算给数据行加共享锁,事务在给一个数据行加共享锁之前必须先取得该表的IS锁;
  • 意向排它锁(IX):事务打算给数据行加排它锁,事务在给一个数据行加排它锁之前必须取得该表的IX锁。

【说明】

  • 1). 共享锁和排它锁都是行锁,意向锁是表锁,应用中我们只会使用到共享锁和排它锁,意向锁是MySQL内部使用的,不需要用户干预。
  • 2). 对于update、delete和insert语句,InnoDB会自动给涉及的数据集加排它锁; 对于普通的select语句,InnoDB不会加任何锁,事务可以通过以下语句显示的给记录集加共享锁和排它锁。
    • 共享锁:select * from table_name where … lock in share mode;
    • 排它锁:select * from table_name where … for update;
    • 对于锁定记录后需要进行更新操作的应用,应该使用select...for update方式来获取排它锁(如果使用共享锁的话,那么在读了之后再去写会发生阻塞,导致死锁)
  • 3). InnoDB存储引擎的行锁是通过给索引上的索引项加锁来实现的,因此InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才会使用行级锁,否则InnoDB将会使用表锁。

3. 抽象锁

【说明】

  • 乐观锁和悲观锁都是【抽象性】的,不真实存在的
  • 乐观锁和悲观锁都是针对读【select】来讲的

3.1 悲观锁

2.6.1 概念

悲观锁:在整个的数据处理阶段,将数据锁定。

2.6.2 悲观锁的实现

悲观锁是通过数据库来实现的,其实,简单来说,就是排它锁。

2.7 乐观锁

2.7.1 概念

乐观锁:假设不会发生并发冲突,只在提交操作的时候检查是否违反了数据的完整性。
参考 —> 事务第02讲:数据库事务(MySQL) 的 [3.2.2.3.2 解决方案二(乐观锁)]

2.7.2 乐观锁实现

乐观锁通过表设计和代码来实现

方案:
——————————————————————————————————
最长使用的方法就是 版本列法(事务第02讲:数据库事务(MySQL) 的 [3.2.2.3.2 解决方案二(乐观锁)])。
——————————————————————————————————
通过为数据库表增加一个 version版本字段 或者 timestamp时间戳字段。当读取数据时,将version字段的值一起读取,数据每更新一次,都对version值加1。当我们提交更新的时候,判断数据库表对应记录的当前版本号与读取时的version是否相同。如果相同,则更新,否则认为是过期数据。

栗子:
——————————————————————————————————

  • 1). 表users,字段:id、name、version
  • 2). 实现过程
    • 读取表数据,select * from users; 得到的version为versionValue。
    • 更新,为了防止发生冲突,sql语句为,update users set name = ‘haha’, version = versionValue + 1 where version = versionValue;

问题:
—————————————————————————————————————
乐观锁,保证了当前事务修改的数据和第一次查询出来的数据是一致的。但是,会存在更新失败的情况。如果出现这种情况,则说明在更新之前,有其他的事务已经更新了记录,这种情况下,可以通过【重试】来保证更新成功。所以说,乐观锁还是有风险的,毕竟有可能会出现连续好几次都更新失败。

问题验证:

session_1 session_2
开始事务,begin transaction; 开始事务,begin transaction;
select * from users; 查询结果version为1。 select * from users; 查询结果version为1。
update users set name = ‘s1’, version = 1 + 1 where version = 1;
commit; 提交事务后,version更新为2
update users set name = ‘s1’, version = 1 + 1 where version = 1; 其实这条sql压根就不会更新任何数据

5. InnoDB-MVCC与乐观锁

MVCC:Multi-Version Concurrency Control(多版本并发控制)的缩写。MVCC并不是乐观锁,InnoDB所实现的MVCC才是乐观锁(具体实现参见2.7.2 乐观锁实现)。

具体的验证参见《事务第02讲:数据库事务(MySQL)》的 5.3 可重复读 章节。

MySQL中的InnoDB存储引擎的默认事务隔离级别是RR(repeatable-read),而可重复读的隔离级别下MySQL使用了MVCC。

6. 死锁

99.参考资料

  • MySQL学习之——锁(行锁、表锁、页锁、乐观锁、悲观锁等)
  • MySQL锁详解
  • 乐观锁以及乐观锁的实现
  • 对mysql乐观锁、悲观锁、共享锁、排它锁、行锁、表锁概念的理解
  • 浅谈Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景

该睡觉了,未完待续,明天再写吧,晚安了各位,么么哒(*  ̄3)(ε ̄ *)

下一篇文章链接 — 事务第02讲:数据库事务(MySQL)

你可能感兴趣的:(Transaction)