数据库事务及其特性

  • 数据库事务是指一系列数据库操作被视为一个单独的逻辑单元,要么全部成功执行,要么全部失败回滚。事务的目的是确保数据的完整性和一致性,即使在多个并发访问的情况下也保证数据的正确性。
  • 在数据库中,事务通常由以下四个属性组成,即为ACID
  1. 原子性:一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,而且如果有出错,就会通过undo log回滚到事务开始前的状态。
  2. 一致性:一个事务在操作前和操作后,数据满足完整性约束,数据库保持一致性状态。eg:用户A转账200给用户B,A+B的总钱数不变。
  3. 隔离性:数据库允许多个并发同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,因为多个事务同时使用相同的数据时,不会相互干扰,每个事务都有一个完整的数据空间,对其他并发事务是隔离的。
  4. 持久性:事务处理结束后,对数据的修改就是永久的,即使系统故障也不会丢失。

InnoDB引擎是通过什么技术来保证事务的四个特性呢?

  1. 持久性:是通过redo log(重做日志)来保证的;
  2. 原子性:是通过undo log(回滚日志)来保证的;
  3. 隔离性:是通过MVCC(多版本并发控制)或锁机制来保证;
  4. 一致性:是通过持久性,原子性,隔离性来保证的。

并发事务会引发什么问题?

  • MySQL服务器是允许多个客户端连接的,意味着MySQL会出现同时处理多个事务的情况。那么在同时处理多个事务的时候,就可能出现脏读,不可重复读,幻读。
  1. 脏读:如果一个事务读到另一个未提交事务修改过的数据,就意味着发生了脏读现象。
  2. 不可重复读: 在一个事务内连续多次读取同一个数据,如果出现前后两次读取到的数据不一致的情况,就意味着发生了不可重复读现象。
  3. 幻读: 在一个事务内多次查询某个符合条件的记录数量,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了幻读现象。

事务的隔离级别有哪些?

  • 隔离级别应对的现象
  1. 脏读:读到事务未提交的数据
  2. 不可重复读:事务读取数据前后数据不一样
  3. 幻读:前后读取记录的数量不一致
  • SQL提出了四种隔离级别来规避这些现象,隔离级别越高,性能约低
  1. 读未提交:指一个事务还没提交时,它做的变更就能被其他事务看到
  2. 读提交:指一个事务提交之后,它做的变更才能被其他事务看到
  3. 可重复读:指一个事务执行过程中看到的数据,一直跟事务启动时看到的数据一致。
  4. 串行化:会对记录上读写锁,在多个事务对这条记录进行读写操作时,如果发生读写冲突的时候,后访问到的事务必须等前一个事务执行完成,才能继续执行。
    串行化>可重复读>读已提交>读未提交

四种隔离级别具体如何实现?

  1. 对于读未提交隔离级别的事务来说,因为可以读到未提交事务修改的数据,所以之间读取新数据。
  2. 对于串行化隔离级别的事务来说,通过加读写锁的方式可以来并行访问。
  3. 对于读提交和可重复读隔离级别来说,它们是通过Read View来实现的,它们的区别在于创建Read View的时机不同。Read View相当于一个数据快照,读提交是在每个语句执行前都会重新生成一个Read View,而可重复读是启动事务时生成一个Read View,整个事务期间一直用这个Read
    View。
  • 注意,执行[开启事务]命令,并不意味着启动了事务,在MySQL有两种开启事务的命令,分别是:
  1. begin/start transaction命令;
  2. start transaction with consistent snapshot 命令;
    这两种开启事务的命令,事务的启动时机是不同的:
  3. 执行了start transaction命令后,并不代表事务启动了。只有在执行这个命令后,执行了第一条select语句,才是事务真正启动的时机。
  4. 执行了start transaction with consistent snapshot命令,就会马上启动事务。

MySQL可重复读隔离级别完全解决幻读了吗?

没有完全解决幻读,例如以下两个场景

  1. 在可重复读的隔离级别下,事务A第一次执行普通的select语句时生成了一个ReadView,之后事务B向表中新插入一条数据并提交,事务A对这条记录进行更新操作,这是trx_id隐藏列值就变成事务A的id,这时再select查询就可以看到这条记录了,就发生了幻读。所以认为MySQL InnoDB中的MVCC并不能完全避免幻读现象。

  2. 事务A先执行快照读语句得到记录数量,然后事务B插入一个记录并提交,事务A再执行当前读语句就看到了这条记录,也就发生了幻读。
    要避免这类特殊场景下发生幻读的现象的话,就是尽量再开启事务之后,马上执行select……for update这类当前读的语句,因为它会对记录加next-key lock,从而避免其他事务插入一条新纪录。