MySQL的InnoDB如何处理事务

介绍为什么需要事务,什么是ACID特性以及什么是事务隔离级别

为什么需要事务

  1. 数据一致性:当多个用户同时访问数据库时,可能会出现数据不一致的情况。通过使用事务,可以确保每个操作都被完全执行或者完全撤销,从而使得数据库保持一致性。
  2. 数据可靠性:如果一个事务在处理过程中发生错误,所有对数据库的修改都会被回滚,以便让数据库恢复到原始状态。这避免了在错误状态下导致数据被损坏的风险。
  3. 并发控制:由于MySQL支持并发访问,可能会出现多个用户同时对同一资源进行读写操作的情况。通过使用事务,可以防止并发操作带来的数据冲突问题,从而保证数据的完整性和正确性。

事务特性

将一系列的操作,组成一个工作单元,保证不可分割,全部操作执行成功或全部失败;

ACID

事务的正确执行,需要满足ACID特性:

  1. 原子性 Atomicity:一个事务必须被视为一个不可分割的最小工作单元,整个事务中所有操作,要么全部成功,要么全部失败;
  2. 一致性 Consistency:保证任何情况下,事务执行前后的数据正确性;不会因为并发问题,导致数据错误;
  3. 隔离性 Isolation:一个事务在最终提交前,所变更的数据对其他事务是不可见的;保证并发场景下,数据的一致性;
  4. 持久性 Durability:一旦事务提交完成,修改将持久化到数据库中;

InnoDB是如何保证ACID

  1. 原子性:InnoDB通过
    Undo Log
    保证事务的原子性;实现回滚的操作;
  2. 隔离性:通过锁、MVCC保证事务的隔离性:
    • 写并发:由锁来控制;
    • 读并发:由MVCC来控制;
  3. 持久性:
    Redo Log
    Undo Log
    来保证;
  4. 一致性:在以上的特性保证的前提下,一致性也就得到了保证;

事务的隔离级别

事务隔离级别,是一种Trade Off的结果;

并发数据读写,可能存在:脏读不可重复读幻读等问题;

但是如果完全隔离,比如串行化执行,又会带来并发性能问题;

因此:通过设定不同的隔离级别,应对不同的并发场景;

并发读问题

  1. 脏读:一个事务对数据进行增删改,但并没有提交,另一个事务却能读到未提交的数据;
  2. 不可重复读:前后多次读取,数据内容不一致;一事务对数据进行了更新或删除操作,提交后,另一事务中再次读取同一个数据,结果不一致;
    • 解决不可重复读,关键在于事务间的数据可见范围;
    • MVCC通过ReadView的生成时机
  3. 幻读:前后多次执行同样的读操作,返回的数据量不,通常是范围操作时,期间被其他事务插入或删除数据;

隔离级别

隔离级别                    级别          脏读  不可重复读幻读                
RU 读未提交无保证        存在  存在      存在                
RC 读已提交语句级        不存在存在      存在                
RR 可重复读事务级        不存在不存在    存在(解决了部分幻读)
串行化      最高级,效率低不存在不存在    不存在              
  • RU:Read Uncommited
  • RC:Read Committed
  • RR:Repeatable Read

不同的隔离级别如何解决并发问题

脏读:读取了未提交事务的数据,事务间完全没有隔离,就会发生脏读;

  • MVCC通过活跃事务和版本链,保证了当前事务不会读取到其他活跃事务的数据版本;

不可重复度:其他事务提交后,当前事务再次读取数据不一致;

  • RC隔离级别每次执行一致性读,MVCC都会生成新的ReadView,就会读取到最新的可读版本;
  • RR隔离级别只生成一次ReadView,其他事务提交,也不会污染当前事务的ReadView,保证了每次读取数据一致;详见:[[05-MySQL-读写并发#MVCC#ReadView的生成时机]]

幻读:当前事务锁定了多条数据,其他事务插入了新的数据,导致当前事务读取了更多的数据;

  • RC隔离级别仍然存在此问题,MVCC都会生成新的ReadView;
  • RR隔离级别需要分情况:
    • 如果当前事务仅有一致性读,不会发生幻读,ReadView只有一个;
    • 如果当前事务仅有当前读,不会发生幻读,临键锁Next-Key Lock会锁定当前事务操作的数据范围,阻塞其他事务对此范围的数据写入;
    • 如果当前事务混合使用一致性读当前读,会发生幻读;

如何解决幻读:加锁

如果业务操作是先select,后update,并且存在多线程操作,只能在读的时候加锁防止其他事务插入,强制同步处理;

SELECT ... FOR UPDATE