掘金 后端 ( ) • 2024-04-14 12:09

深入探讨了数据库事务的原子性、隔离性和持久性,以及MySQL如何通过其机制来确保这些特性得到满足。对于理解数据库事务处理和MySQL的内部工作机制非常有帮助。

如果无法保证原子性会怎么样?

原子性是指事务包含的所有操作,要么全部完成,要么全部不完成。如果不能保证原子性,可能会出现以下问题:

  • 数据不一致:事务中的部分操作可能对数据做出了更改,而其他操作由于某种原因(如系统故障、操作错误等)未能完成,导致数据状态不一致。
  • 资源泄露:如果一个操作分配了资源(如内存或文件描述符)而未能成功地释放或回滚,可能会导致资源泄露。
  • 系统可靠性下降:当多个事务相互依赖时,一个事务的部分完成可能导致其他事务无法继续,影响系统的整体可靠性。

假如你在网上购物,支付环节需要从你的银行账户扣款,并将相应金额增加到商家账户。如果扣款成功,但增款失败,没有原子性的保护,你的账户会减少相应的金额,而商家却没有收到钱,导致交易不公平。

MySQL怎么保证原子性的?

MySQL通过事务(Transaction)来保证原子性。事务是一个不可分割的工作单位,事务内的操作要么全部完成,要么全部不完成,这就是原子性。

在MySQL中,为了保证事务的原子性,它使用了以下两种主要的技术:

  1. Undo Log(撤销日志): Undo Log记录了事务执行前的旧数据信息,当事务执行过程中出现错误,或者用户执行ROLLBACK语句进行事务回滚时,可以利用Undo Log中的信息将数据库恢复到事务开始前的状态

  2. Redo Log(重做日志): 当MySQL异常宕机时,Redo Log可以用来恢复正在执行中的事务。它记录了事务中所有的修改操作,用于在MySQL重启后,重新执行这些操作以保证数据一致性。

这两种日志机制的结合使用,即在出现错误回滚中使用Undo Log,和在系统崩溃时利用Redo Log进行恢复,使MySQL可以成功保证事务的原子性。

需要注意的是只有使用了InnoDB存储引擎的MySQL支持事务,MyISAM存储引擎并不支持事务。

如果无法保证隔离性会怎么样?

如果数据库无法保证事务的隔离性,将会出现以下并发问题:

  1. 脏读(Dirty Reads):当一个事务可以读取另一个事务未提交的数据时,若后者回滚,则前者读取到的就是不正确的数据。
  2. 不可重复读(Non-repeatable Reads):在一个事务的两次查询之间,另一个并发事务进行了更新操作,导致第一个事务两次读到的数据不一致。
  3. 幻读(Phantom Reads):当一个事务重新读取之前查询过的数据范围时,发现有其他事务新增的记录,看似产生了"幻影"数据。
  4. 丢失更新(Lost Updates):两个事务同时读取相同的数据并更新它,可能会导致其中一个事务的更新被覆盖。

例如,如果银行系统无法保证隔离性,在处理多个客户的转账操作时可能会出现资金计算错误。一个客户的转账可能会基于另一个客户正在处理但未完成的交易,最终导致资金总额的不一致。

MySQL怎么保证隔离性的?

MySQL保证隔离性主要是通过所谓的“隔离级别”实现的。隔离性是指并发执行的事务之间不相互影响,每个事务都感觉好像自己在独占系统。

在MySQL中,支持四种隔离级别:

  1. 读未提交 (READ UNCOMMITTED):在该级别下,一个事务可以读取到另一个事务修改但还未提交的数据,也被称为"脏读"。这种隔离级别并发性最好,但是一般很少使用,因为它可能读取到未提交的数据。
  2. 读已提交 (READ COMMITTED):这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的基本要求,一个事务只能读取已经提交的数据。
  3. 可重复读(REPEATABLE READ):在同一事务内的查询都能够看到一致的快照数据。也就是说,在事务开始后,无论其他事务如何修改数据,本事务都能看到同样的数据。MySQL默认的隔离级别就是REPEATABLE READ。在该级别下,除了防止"脏读",还防止了"不可重复读"。
  4. 串行化(SERIALIZABLE):这是最高的隔离级别,它要求所有事务都串行执行。也就是说,同一时间只能有一个事务在执行。虽然可以提供最高级别的隔离性,但是在并发环境中性能开销也是最大的。

在InnoDB存储引擎中,另一个叫做"多版本并发控制(MVCC)"的技术也对事务的隔离色提供支持。每次对数据的更新操作,InnoDB都会为该数据创建一个新的版本,而不是在原数据上直接修改,这样就可以通过读取旧版本的数据来避免阻塞读操作,增强并发性能,从而保证隔离性。

如下图所示为隔离级别与并发问题的关系:

隔离级别 脏读 不可重复读 幻读 未提交读(READ UNCOMMITTED) 允许 允许 允许 已提交读(READ COMMITTED) 阻止 允许 允许 可重复读(REPEATABLE READ) 阻止 阻止 允许 可串行化(SERIALIZABLE) 阻止 阻止 阻止

如果无法保证持久性会怎么样?

如果无法保证持久性,那么在事务提交后,如果发生系统崩溃或者其他故障,那么这个事务的修改结果可能会丢失,没有被永久的保存在数据库中。

持久性是指当事务被认为是已经结束时,对数据的改变就是永久性的。该特性保证了即使在系统崩溃或者电源故障后,已经提交的数据仍然可以保持,不会丢失。

比如:假设某电商网站使用MySQL数据库来存储用户订单信息。在某个高峰时段,小豆在网站上下了一个订单,订单包含了她选择的商品、数量、价格等详细信息。这个订单首先被创建并存储在数据库的内存缓冲区中,等待最终被写入到磁盘上的数据库文件,在订单信息被写入磁盘之前,停电了。这时候当系统重启后,订单信息将丢失

MySQL是如何保证持久性的?

MySQL通过"写前日志"(Write-Ahead-Logging,即WAL)策略,确保了事务的持久性。持久性是指一旦事务完成(即,commit),对数据的修改就是永久性的。即便发生系统崩溃,修改的数据也不会丢失。

具体实现如下:

  1. Redo Log(重做日志): 这是MySQL中最核心的保证持久性的机制。当InnoDB数据库引擎进行任何改变数据的操作时,首先会把这些操作写入到内存中的Redo Log Buffer,然后再按照一定频率(如每秒、事务提交时、buffer满了等)将这些操作永久的记录到磁盘的Redo Log中,而不是直接将修改操作写到磁盘的数据文件。当数据库异常重启时,虽然数据文件没有来得及持久化的更改可能丢失,但是redo log已经记录了所有改变数据的操作,MySQL可以通过_redo_操作进行数据恢复,以保证数据的持久性。

  2. Binlog(二进制日志): 这是MySQL服务器层的持久性保证机制,在每次事务提交的时候,会把事务的所有操作(包括SQL语句)写入到Binlog中,同时写入磁盘保证持久性。在数据主从同步或者数据恢复时,可以重放Binlog中记录的所有操作。

这两种机制,尤其是Redo Log,保证了MySQL的持久性,即使在数据库崩溃后也能通过重播日志恢复数据,满足ACID中的持久性需求。