[数据库] 事务隔离级别、对应解决的并发读写问题
一个良好的数据库系统,其事务应当具备以下特性:
原子性、一致性、隔离性、持久性。
1.可能产生的问题:
若多个事务对同一个数据进行更新或查询,会产生:脏写、脏读、不可重复读、幻读四种问题
1.脏写
假设两个事务A和B准备对同一数据进行写。
A在检查数据无误后进入写如数据代码,此时线程切换至B,B将目标数据更新(并提交),随后线程切换回A,A继续进行写操作。如此可能带来这些问题:
A. 若A事务随后回滚数据,会导致已经提交的B事务数据丢失。
B. 某些数据结构(如哈希表)采取了冲突解决方法,在这种情况下会导致数据丢失。若哈希表未能正确括链。
2.脏读
B正在试图查找数据,此时线程切换至A,A将目标数据更新,随后线程切换回B,B读取到新的之后进行后续操作。但A之后回滚了事务。导致B获取的数据与数据库中的数据实际不统一。
脏读脏写的核心问题在于更新或查询事务插入到了另一个事务操作过程中,由此会带来数据不一致的问题。
3.不可重复读
进行事务A中希望进行多次数据读取,但是由于其他事务对数据不停地进行更新,导致A每次读取同一个数据时得到的结果不一致。
4.幻读
类似于不可重复读,不过针对的查询一组数据的情况,由于新的插入事务的多次读取事务间生效,导致每次查询都会得到更多的数据,看起来就想产生了读取上的幻觉(所以感觉似乎读取到了更多的数据)。
对比:不可重复读是指重新查询后之前的数据项消失(被删除或更新),幻读是指重新查询后得到了之前没见过的新数据。
2.锁的应用
排他锁(写锁),仅能被一个事务持有,加锁期间其他事务不得对对象加任何锁。
共享锁(读锁),可被多个事务持有,加锁期间其他事务不得对对象加写锁(也就不能进行数据更新)。
3.事务隔离级别
3.1读未提交(Read Uncommitted)
最基本的级别,当一个事务开始写操作时,其他事务不可同时进行写操作,但允许读。
修改数据需要加写锁,但读取不需要加锁。
可以解决脏写问题,但脏读、不可重复读、幻读依然会出现。
此级别下,查询数据时总是获取最新版本的记录。
3.2读已提交(Read Committed)
读取事务时允许其他事务访问数据,但未提交的写事务不会被其他事务立即读取。
可以解决脏读、脏写问题。
此级别下,查询数据时需要先获取最新版本记录的锁。在每次读取数据前都会生成一个ReadView。
3.3可重复读取(Repeatable Read)
读取事务允许其他读取事务,但不允许写事务。写事务禁止其他事务一同执行。MySQL(MVCC)机制默认为该级别。
可以解决脏读、脏写、不可重复读。但有时依然会出现幻读。
此级别下,查询数据前(整个事务中首次读取数据前),会生成一个ReadView,其中包括当前活跃的读写事务id,访问数据前,需要判断访问字段版本的trx_id,若小于ReadView中的最小事务id,则说明该版本在ReadView生成前即已提交,可以被访问。如果大于ReadView中的最大事务id,说明该版本是在ReadView生成后产生,不能被访问。否则,则查询对应的事务id是否还活跃,如果活跃则说明该版本不能被访问。
当确认该版本记录不可见时,需要顺着版本链找到下一个版本,直到找到最新的可见版本(也有可能为空)。
3.4串行化(Serializable)
将对同一数据进行访问的事务完全串行执行。可以解决所有问题。