事务第01讲:数据库锁[MySQL]
部分资料
引用自各路大神,具体见“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】,查询表数据
2). 【客户端A】对数据加S锁
3). 【客户端A】对 u_id = ‘0’ 的数据加了S锁,此时,我客户端B也可以对该记录加S锁。同上图。
4). 【客户端A】对锁定的记录进行更新操作,可以看出在等待锁
。
5). 此时,【客户端B】也对该条记录进行更新操作,则会出现死锁,然后退出
。
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锁
2). 【客户端B】只能查询该数据,如果再对该数据加X锁,则就处于锁等待状态
。
3). 【客户端A】对锁定记录执行更新操作后,显示提交(commit),提交后就会释放锁。
4). 当【客户端A】释放锁后,【客户端B】在第2步执行的for update就会立即执行成功。
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.参考资料
该睡觉了,未完待续,明天再写吧,晚安了各位,么么哒(*  ̄3)(ε ̄ *)
下一篇文章链接 — 事务第02讲:数据库事务(MySQL)