可重复读(Repeatable read)能防住幻读吗?
文章目录
可重复读(Repeatable read)能防住幻读吗?
事务隔离级别
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | √ | √ | √ |
读已提交(read-committed) | × | √ | √ |
可重复读 (repeatable-read) | × | × | √ |
串行化 (serializable) | × | × | × |
- 事务隔离级别为读已提交时,写数据只会锁住相应的行。
- 事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。
- 事务隔离级别为串行化时,读写数据都会锁住整张表。
- 隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
mysql默认的事务隔离级别为 repeatable-read
Oracle默认 读已提交(read-committed)
select @@tx_isolation;
set tx_isolation = '';
--jdbc 设置(1 2 4 8)
Connection.setTransactionIsolation(int level)
事务的并发问题
概念
- 脏读: 事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。(回滚造成)
- 不可重复读: 事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。(提交造成)(update)
-
幻读: 前后多次读取,数据总量不一致。(delete、insert)
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于插入或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。
幻读和不可重复读的区别
- 不可重复读: 重点是修改,在同一事务中,同样的条件,第一次读的数据和第二次读的数据不一样。(一个事务多次读取同一范围内数据时,另外一个发生了事务发生insert操作并提交了)。数据变化。
- 幻读: 重点在于插入或者删除:在同一事务中,同样的条件,,第一次和第二次读出来的记录数不一样。(一个事务多次读取同一条数据时,另外一个发生了事务发生insert,delete操作并提交了。update操作一条新数据并不会影响刚才被读的那条数据。)。行数变化。
乐观锁与悲观锁
悲观锁
悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。
乐观锁
乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。
悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但是这样会带来极大的数据库性能开销,对于长事务来说,这种开销是无法承受的。
悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。
数据版本
乐观锁大多是基于数据版本实现。
数据版本即为为数据增加一个版本标识,一般是为数据库表增加一个“version”字段来实现。读取出数据时,会将该版本号一同带出,之后更新时,版本号+1。提交数据的版本数据与数据库表对用的记录的当前版本信息进行对比,如果提交的数据版本号大于当前数据库表的版本号,则进行更新操作,否则会认为其是过期数据。
MVCC
在InnoDB中,会在每行数据后添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期或者被删除。
在可重复读 (repeatable-read)事务隔离级别下:
- select:读取创建版本号<=当前事务版本号。
- INSERT时,保存当前事务版本号为行的创建版本号。
- DELETE时,保存当前事务版本号为行的删除版本号。
- UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行。
每行记录都需要额外的存储空间来记录version,增加了行检查与维护工作,但是可以减少锁的适用,读取数据操作简单,性能好。通过MVCC读取的数据其实是历史数据,而不是最新数据。这种读取数据的方式叫做快照读(snapshot reda),只使用select时就是快照读。
当前读
update、insert、delete都是采用的当前读的方式。
Next-Key锁
B+树的特点是所有数据都存储到叶子节点上,当我们对某条数据进行更新后,就会对这条数据加行锁,能够防止其他事务对其进行update与delete,还会加GAP锁。而它的前后并没有锁住,这样就极大的提高了数据的并发能力。**