事务中的加锁方式
1、事务的四种隔离级别
隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
未提交读(Read uncommitted) | 可能 | 可能 | 可能 |
已提交读(Read committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable read) | 不可能 | 不可能 | 可能 |
串行化(Serializable ) | 不可能 | 不可能 | 不可能 |
1.读未提交
事务1还没提交,事务2就能读到事务1修改的内容
2.读已提交
读一个事务提交后的数据。
但是事务2在事务1提交前和提交后,读的数据是不一样的。e.g“不可重复读”
事务1查询,但是事务2又插入新的记录,原先事务1再次查询时,会把后来插入的记录也读出来。e.g“幻读”
3.可重复读
MySQL帮我们解决掉了幻读问题。
4.串行化
只允许同一条记录进行修改。
2、MySQL中锁的种类
MySQL中锁的种类很多,有常见的表锁和行锁,也有新加入的Metadata Lock等等,表锁是对一整张表加锁,虽然可分为读锁和写锁,但毕竟是锁住整张表,会导致并发能力下降,一般是做ddl处理时使用。
行锁则是锁住数据行,这种加锁方法比较复杂,但是由于只锁住有限的数据,对于其它数据不加限制,所以并发能力强,MySQL一般都是用行锁来处理并发事务。这里主要讨论的也就是行锁。
1)、Read Committed(读取提交内容)
数据的读取都是不加锁的,但是数据的写入、修改和删除是需要加锁的。
2)、Repeatable Read(可重读)
这是MySQL中InnoDB默认的隔离级别。我们姑且分“读”和“写”两个模块来讲解。
a、读
####不可重复读和幻读的区别####
很多人容易搞混不可重复读和幻读,确实这两者有些相似。但不可重复读重点在于update和delete,而幻读的重点在于insert。
如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。
所以说不可重复读和幻读最大的区别,就在于如何通过锁机制来解决他们产生的问题。
上文说的,是使用悲观锁机制来处理这两种问题,但是MySQL、ORACLE、PostgreSQL等成熟的数据库,出于性能考虑,都是使用了以乐观锁为理论基础的MVCC(多版本并发控制)来避免这两种问题。
读可提交和可重复读的实现原理
每行数据通过事务ID来识别
每次修改会用这种方式记录,最新版本用链接连起来
事务ID为200时,就能够读到这行数据为1,4,1,1,a3
但是当事务ID为300时,没有一条ID符合当前ID,就要用到ReadView
select, readview[100,200]
在select的时候,里面存的是还没有提交的事务
读已提交,每次读会产生一个readview,readview里面存的是活跃的记录,查询时会排除这些记录
可重复读,每次读的时候,使用的都是第一次查询时产生的readview
1.select, readview[100,200]
// b commit
2.select, readview[100,200]
实现方式都是通过版本链和readview,
区别在于readview的产生时间
读已提交,是在每次select,都会产生一个readview。
每次查询开始,都把现在没有提交的事务,都存在readview里面。
比方,第二次查询ID200已经提交,那么readview[100],减少了。
可重复读,readview只会在第一次读的时候产生。之后使用的readview都是第一次产生的readview
读锁、写锁
for update
加了写锁,但是还能够select查询
锁必须在事务内部使用
事务结束之后,锁也会被释放
行锁
在read commited下
- 查询使用主键
总结:查询使用的是主键时,只需要在主键值对应的那一个条数据加锁即可。 - 查询使用唯一主键
总结:查询使用的是唯一索引时,只需要对查询值所对应的唯一索引记录项和对应的聚集索引上的项加锁即可。 - 查询使用的普通索引
总结:查询使用的是普通索引时,会对满足条件的索引记录都加上锁,同时对这些索引记录对应的聚集索引上的项也加锁。 - 查询没有用到索引
间隙锁:RR级别中,事务A在update后加锁,事务B无法插入新数据,这样事务A在update前后读的数据保持一致,避免了幻读。这个锁,就是Gap锁。
总结:查询的时候没有走索引,也只会对满足条件的记录加锁。
在repeatble read下
- 查询使用主键
- 查询使用唯一索引
和RC级别一样 - 查询使用普通索引
必然会加锁,间隙锁
这种会加锁,因为在e='a’和e='b’交界处,也是间隙锁
这种不会加锁,因为不在e='b’交界处
总结: REPEATABLE READ级别可以解决幻读,解决的方式就是加了GAP锁。
- 查询没有用到索引
总结:查询的时候没有走索引,会对表中所有的记录以及间隙加锁。
因为所有行的记录都可以修改