目录
1.对脏读,幻读,不可重复读的理解
"脏读"、"幻读"和"不可重复读"是数据库事务隔离级别的问题,它们描述了在并发环境下可能发生的一些问题。让我们逐个来理解这些概念:
-
脏读(Dirty Read) :脏读指的是一个事务读取了另一个事务未提交的数据。在这种情况下,如果那个事务最终回滚了,那么读取的数据就是无效的,因此,脏读就像读到了"脏数据"一样。
-
幻读(Phantom Read) :幻读指的是在一个事务中,两次查询同样的条件,但是得到的结果集不同。这是由于另一个事务在两次查询之间插入(或删除)了数据,导致第二次查询看到的数据比第一次查询多(或少)。幻读通常发生在并发的插入和删除操作中。
例如,事务A在查询某个范围的数据时,事务B插入了符合该范围的新数据,导致事务A的两次查询返回的结果集合不一致,就发生了幻读。
- 不可重复读(Non-Repeatable Read) :不可重复读指的是在一个事务中,两次查询同样的数据,但是得到了不同的结果。这是由于在两次查询之间,另一个事务修改或删除了数据,导致第二次查询看到的数据和第一次查询不一样。就像是时间一样,第二次看表的时候,不可能得到第一次看表的时间。
2.数据库事务隔离级别
这些问题的出现是由于数据库事务隔离级别不同导致的。数据库系统提供了不同的隔离级别,每个隔离级别决定了事务在并发执行时,是否能够看到其他事务未提交的数据,以及是否能够避免幻读和不可重复读的问题。
- READ UNCOMMITTED:最低的隔离级别,允许脏读、幻读和不可重复读。
- READ COMMITTED :允许不可重复读和幻读,但是避免了脏读。读已经提交了的
- REPEATABLE READ :避免了脏读和不可重复读,但是允许幻读。可重复读取
- SERIALIZABLE:最高的隔离级别,避免了脏读、不可重复读和幻读,但是性能较差,因为它会对所有的查询加锁。
3.如何避免脏读,幻读,不可重复读
3.1如何避免脏读
- 数据库隔离级别:读已提交
- 保证事务的原子性:要么一起成功,要么一起失败
- 使用乐观锁:在一些场景下,可以使用乐观锁的方式来避免脏读。乐观锁是通过版本号或时间戳等方式来实现的,当一个事务要更新某行数据时,它会先检查这行数据的版本号或时间戳是否与自己持有的一致,如果一致,则可以更新,否则说明已经被其他事务修改过。
- 使用数据库的悲观锁:悲观锁是数据库系统提供的锁机制,可以在读取数据时加锁,阻止其他事务对相同数据的修改,从而避免脏读。
- 合理设计事务边界:将事务的范围控制在最小范围内。不要在一个事务中包含不必要的读操作,以减少事务持有锁的时间,从而减少其他事务被阻塞的可能性。
- 避免长事务:长时间持有事务会导致锁定资源,增加其他事务被阻塞的可能性。因此,应该尽量避免长事务的存在。
3.2对乐观锁的理解
乐观锁是一种基于数据版本控制的并发控制机制。与悲观锁(在读取数据时就会对数据加锁)不同,乐观锁是在数据更新时才会对数据进行加锁。乐观锁的核心思想是,假设在事务开始时,不会有其他事务来修改数据,因此在事务提交时,会检查在此期间数据是否被其他事务修改。如果没有被修改,则提交成功;如果被修改了,则需要处理冲突。
如果更新操作失败(即版本号或时间戳不一致),需要根据具体情况决定如何处理冲突。常见的处理方式包括回滚事务、重新读取数据并合并、提示用户解决冲突等。 最终目的就是为了保证数据的一致性。
3.3如何避免幻读
-
使用
SERIALIZABLE
隔离级别 :SERIALIZABLE
隔离级别是数据库中最高的隔离级别,它能够避免脏读、不可重复读和幻读。但是,这种隔离级别会对性能产生较大的影响,因为它会对所有的查询加锁,通常需要谨慎使用。 -
使用乐观锁
-
使用悲观锁 :悲观锁是一种在读取数据时就加锁的机制,可以避免其他事务对相同数据的修改。在关键查询中使用FOR UPDATE(对于MySQL等数据库)或者SELECT ... FOR UPDATE(对于Oracle等数据库)语句,将数据加上排它锁,确保其他事务无法修改这些数据。
-
使用范围锁 :范围锁是指在一个范围内对数据进行加锁。例如,对于某个表的某个范围的数据进行查询和更新时,可以使用范围锁确保在这个范围内的数据不会被其他事务修改。
-
使用快照隔离(Snapshot Isolation) :快照隔离是一种在事务开始时创建数据的快照,并在事务中使用该快照来保证读取的一致性。这样可以避免不可重复读和幻读的问题。
3.4如何避免可重复读
- 事务的原子性
- 使用乐观锁
- 使用悲观锁:在读的时候上锁
- 尽量减少事务的持有时间:长时间持有事务会导致锁定资源,增加其他事务被阻塞的可能性。因此,应该尽量减少事务的持有时间,只在必要的时候才开启事务。
3.5总结
综上所述,避免脏读,幻读和不可重复读,需要做到
1.加乐观锁
2.加悲观锁
3.尽量减少事务的持有时间
4.合理设计事务边界
5.避免长事务
6.保证事务的原子性
7.使用合适的隔离级别