数据库事务,隔离级别,脏读,不可重复读,幻读。

数据库事务特征:ACID

原子性(Atomicity):

一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节.

一致性(Consistent):

在事务开始之前和完成之后,数据都必须保持一致状态,必须保证数据库的完整性。也就是说,数据必须符合数据库的规则。

隔离性(Isolation):

数据库允许多个并发事务同时对数据进行操作,隔离性保证各个事务相互独立,事务处理时的中间状态对其它事务是不可见的,以此防止出现数据不一致状态。可通过事务隔离级别设置:包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)

持久性(Durability):

事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

MySQL 数据隔离级别:

  1. Read uncommittied(读未提交数据)
  2. Read committied(读已提交数据)
  3. Repea read(可重复读)
  4. Serializable(可序列化)

在 InnoDB 中,默认为 Repeatable (可重复的)级别,InnoDB 中使用一种被称为 next-key locking 的策略来避免phantom(幻读)现象的产生。

使用 select @@tx_isolation; 可以查看 MySQL 默认的事务隔离级别。

不同的事务隔离级别会导致不同的问题:

隔离级别 脏读 不可重复读 幻读
Read uncommittied(读未提交数据)
Read committied(读已提交数据) ×
Repea read(可重复读) × ×
Serializable(可序列化) × × ×

事务七种传播行为:

事务传播行为类型 说明
PROPAGATION_REQUIRED 如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务。这是最常见的选择。
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRED_NEW 新建事务,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行操作,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

脏读、幻读、不可重复读的概念

脏读:脏读是指一个事务中访问到了另外一个事务未提交的数据

所谓脏读是指一个事务中访问到了另外一个事务未提交的数据,如下图:

事务A 事务B
账户5000 账户5000
小明老婆花了3000
小明查看账户2000(产生脏读)
取款操作发生未知错误,事务B回滚,账户还是5000(rollback)
commit commit

事务B更新账户为2000,但是在事务B commit之前,事务A查看账户时是事务B还未提交的数据,此时事务B出现错误执行rollback,而事务A拿到的还是2000,这就是脏读。

不可重复读:同一个事务中重复读取时获得的数据不同

事务A 事务B
账户5000 账户5000
小明查看账户5000
小明老婆花了3000(修改该账户)
commit
小明查看账户2000
commit

在事务A中事务B修改了该账户,所以两次得到的值不同。

幻读:幻读是不可重复读的特殊场景,但是事务二的数据操作仅仅是插入和删除,不是修改数据,读取的记录数量前后不一致

事务A 事务B
查询id>100的用户(只有102)
插入id=101的用户(John)
commit
插入id=1的用户(主键冲突,幻读)
commit

幻读和不可重复读的区别:

由于很多人(当然也包括本人), 容易搞混 不可重复读幻读, 这两者确实非常相似。

  • 不可重复读 主要是说多次读取一条记录, 发现该记录中某些列值被修改过。
  • 幻读 主要是说多次读取一个范围内的记录(包括直接查询所有记录结果或者做聚合统计), 发现结果不一致(标准档案一般指记录增多, 记录的减少应该也算是幻读)。(可以参考MySQL官方文档对 Phantom Rows 的介绍)

​ 其实对于 幻读, MySQL的InnoDB引擎默认的RR级别已经通过MVCC自动帮我们解决了, 所以该级别下, 你也模拟不出幻读的场景; 退回到 RC 隔离级别的话, 你又容易把幻读不可重复读搞混淆, 所以这可能就是比较头痛的点吧!
具体可以参考《高性能MySQL》对 RR 隔离级别的描述, 理论上RR级别是无法解决幻读的问题, 但是由于InnoDB引擎的RR级别还使用了MVCC(多版本并发控制), 所以也就避免了幻读的出现!

总结:

(1)不可重复读是读取了其他事务更改的数据,针对update操作

解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。

(2)幻读是读取了其他事务新增的数据,针对insert与delete操作

解决:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。

解决方案:

解决方案也就是上文提到的四种隔离级别,他们可以最大程度避免以上三种情况的发生:

  • 读未提交(Read Uncommitted):允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他锁”(写锁)实现。
  • 读提交(Read Committed):允许不可重复读取,但不允许脏读取。这可以通过“共享锁”(读锁)和“排他锁”(写锁)实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
  • 可重复读取(Repeatable Read):禁止不可重复读取和脏读取,但是有时可能出现幻读数据。这可以通过“共享锁”(读锁)和“排他锁”(写锁)实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
  • 序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

​ 隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

扩展:

第一类丢失更新(回滚丢失,Lost update) : A事务撤销时,把已经提交的B事务的更新数据覆盖了。

事务A 事务B
查询账户余额1000
查询并汇入1000元,余额改为2000元
commit
取出1000元,把余额改为0元
撤销事务
余额1000(丢失更新)
第二类丢失更新(覆盖丢失/两次更新问题,Second lost update) : A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失
事务A 事务B
查询账户余额1000
查询账户余额1000
取出1000元,把余额改为0元
commit
汇入1000元
commit
余额2000(丢失更新)

解决方案:

悲观锁和乐观锁

  • 悲观锁(Pessimistic Lock)

每次获取数据的时候,都会担心数据被修改,所以每次获取数据的时候都会进行加锁,确保在自己使用的过程中数据不会被别人修改,使用完成后进行数据解锁。由于数据进行加锁,期间对该数据进行读写的其他线程都会进行等待。

  • 乐观锁(Optimistic Lock)

每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁,但是在更新数据的时候需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。

适用场景:

悲观锁:比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。

乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。

总结:两种所各有优缺点,读取频繁使用乐观锁,写入频繁使用悲观锁。

悲观锁比较适合强一致性的场景,但效率比较低,特别是读的并发低。乐观锁则适用于读多写少,并发冲突少的场景。

注释:

RC级别,提供了读锁和写锁,解决了脏读问题。

RR级别,在已有读锁和写锁的基础上,增加了gap锁,即间隙锁,解决了幻读的问题(间隙锁解决了幻读,由此就能明白到底什么是幻读了)。

你可能感兴趣的:(mysql,java,入门)