MySQL—事务

这里写目录标题

  • 事务四大特性
  • InnoDB 引擎通过什么技术来保证事务的四个特性
  • 并行事务会引发什么问题
    • 脏读
    • 不可重复读
    • 幻读
  • 事务隔离级别
  • 解决幻读
    • 快照读
    • 快照读的语句
    • 当前读
  • 多版本并发控制(MVCC)
    • 什么是MVCC
    • MVCC底层实现

事务是在MySQL引擎层实现的,我们常见的InnoDB引擎是支持事务的

事务四大特性

MySQL 事务的四大特性通常被称为 ACID 特性。ACID 是 Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)和 Durability(持久性)的首字母缩写。这些特性保证了数据库事务的正确性和可靠性。

  1. 原子性(Atomicity):原子性是指事务中的所有操作要么全部成功执行,要么全部失败。如果事务中的某个操作失败,整个事务将回滚到事务开始之前的状态,就像事务中的操作从未发生过一样。

  2. 一致性(Consistency):一致性是指事务的执行必须将数据库从一个一致性状态转换为另一个一致性状态。一致性状态是指数据库中的数据满足所有完整性约束和业务规则。在事务开始和结束时,数据库都应处于一致性状态。

  3. 隔离性(Isolation):隔离性是指并发执行的事务之间相互隔离,一个事务的中间结果不应该被其他事务看到。事务隔离级别可以根据应用的需求进行调整,不同的隔离级别对应不同的并发访问控制策略,可能会引入不同程度的并发问题,如脏读、不可重复读、幻读等。

  4. 持久性(Durability):持久性是指事务一旦被提交,对数据库所做的更改就会永久保存。即使在系统崩溃或者断电等异常情况下,事务的结果也不会丢失。

ACID 特性是保证数据库事务正确性和可靠性的基本原则,它们为数据库提供了一个稳定、一致、可靠的数据访问环境。

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

  • 原子性是通过 undo log(回滚日志) 来保证的;

  • 持久性是通过 redo log (重做日志)来保证的;

  • 隔离性是通过 MVCC(多版本并发控制) 或锁机制来保证的;

  • 一致性则是通过持久性+原子性+隔离性来保证;

InnoDB 引擎通过多种技术来保证事务的四个特性(ACID):

  1. 原子性(Atomicity):InnoDB 使用**日志记录(Undo Log)**来保证原子性。当事务中的操作需要回滚时,InnoDB 通过 Undo Log 还原数据到事务开始之前的状态。在事务提交时,InnoDB 使用两阶段提交(Two-Phase Commit)协议来确保原子性,防止部分操作成功而其他操作失败的情况。

  2. 一致性(Consistency):InnoDB 通过锁定(Locking)和一致性约束(Consistency Constraints)来保证一致性。InnoDB 支持行级锁定(Row-Level Locking)和外键约束(Foreign Key Constraints),这些机制确保在事务执行过程中数据保持一致。同时,通过在事务提交之前执行约束检查,InnoDB 可以确保仅当事务满足所有一致性约束时才进行提交。

  3. 隔离性(Isolation):InnoDB 使用多版本并发控制(MVCC,Multi-Version Concurrency Control)来保证隔离性。MVCC 通过为每个事务提供一个独立的数据快照来实现,使得事务之间不会相互干扰。此外,InnoDB 还提供四种事务隔离级别:READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE,以满足不同应用场景的需求。

  4. 持久性(Durability):InnoDB 使用**重做日志(Redo Log)**来保证持久性。当事务提交时,InnoDB 将事务的所有修改操作记录到重做日志中,然后将重做日志刷新到磁盘。即使在系统崩溃或断电等异常情况下,通过重做日志,InnoDB 仍然可以恢复已提交事务的修改。此外,InnoDB 的 Checkpoint 机制确保数据文件和重做日志的一致性,以保证数据的持久性。

综上所述,InnoDB 引擎通过 Undo Log、锁定、一致性约束、MVCC、Redo Log 和 Checkpoint 等技术来保证事务的四大特性(ACID)。

并行事务会引发什么问题

脏读

如果一个事务「读到」了另一个「未提交事务修改过的数据」,就意味着发生了「脏读」现象。

不可重复读

在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况,就意味着发生了「不可重复读」现象。

幻读

在一个事务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了「幻读」现象。

事务隔离级别

  • 读未提交(*read uncommitted*),指一个事务还没提交时,它做的变更就能被其他事务看到;可能发生脏读、不可重复读和幻读现象;
  • 读提交(*read committed*),指一个事务提交之后,它做的变更才能被其他事务看到;可能发生不可重复读和幻读现象,但是不可能发生脏读现象;
  • 可重复读(*repeatable read*),指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别;可能发生幻读现象,但是不可能脏读和不可重复读现象;
  • 串行化(*serializable* );会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;脏读、不可重复读和幻读现象都不可能会发生。

解决幻读

快照读

在 MySQL 中,快照读(Snapshot Read)是一种读取数据的方式,它依赖于多版本并发控制(MVCC)机制。在快照读中,事务读取的是数据的一个历史版本,而不是当前的实时数据。这意味着,在事务执行期间,即使其他事务对数据进行了更改,快照读也能保证事务读取到的数据保持一致。

快照读主要应用于隔离级别为 REPEATABLE READ(可重复读) 和 SERIALIZABLE (串行化)的事务。在这两个隔离级别下,事务可以看到在其开始时已经提交的其他事务所做的更改,但无法看到在其执行过程中其他事务所做的更改。这可以防止脏读(Dirty Read)和不可重复读(Non-repeatable Read)问题。

在 InnoDB 存储引擎中,快照读是通过以下步骤实现的:

  1. 当事务开始时,InnoDB 为其分配一个唯一的事务标识(Transaction ID)。
  2. InnoDB 使用 undo 日志和 read view 机制来管理数据的历史版本。
  3. 当事务需要读取某行数据时,InnoDB 会根据事务标识和数据的版本信息来判断该行数据在事务开始时的状态。如果该行数据在事务开始时已经存在且尚未被删除,则事务可以读取该行数据的对应版本。

需要注意的是,快照读仅适用于普通的 SELECT 查询。对于加锁的 SELECT 查询(例如 SELECT … FOR UPDATE 和 SELECT … LOCK IN SHARE MODE),InnoDB 会使用当前读(Current Read)方式,即读取数据的最新版本,并对读取到的数据加锁以确保数据的一致性。

快照读的语句

在 MySQL 中,快照读(Snapshot Read)主要应用于普通的 SELECT 查询。当一个事务执行 SELECT 查询时,它会根据事务的隔离级别和多版本并发控制(MVCC)机制读取数据的历史版本,而不是当前的实时数据。这可以确保事务在其执行过程中看到的数据保持一致,从而避免脏读(Dirty Read)和不可重复读(Non-repeatable Read)问题。

以下是一些使用快照读的 MySQL 语句示例:

  1. 普通的 SELECT 查询:

    SELECT * FROM users WHERE id = 1;
    
  2. 嵌套的 SELECT 查询:

    SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE order_id = 1);
    
  3. 聚合函数和分组查询:

    SELECT COUNT(*) FROM users WHERE age > 30;
    SELECT age, COUNT(*) FROM users GROUP BY age;
    

需要注意的是,快照读仅适用于不带锁的 SELECT 查询。对于加锁的 SELECT 查询(例如 SELECT … FOR UPDATE 和 SELECT … LOCK IN SHARE MODE),MySQL 会使用当前读(Current Read)方式,即读取数据的最新版本,并对读取到的数据加锁以确保数据的一致性。

  • 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。

当前读

在 MySQL 中,当前读(Current Read)是一种读取数据的方式,它用于加锁的 SELECT 查询和数据修改操作(如 UPDATE、DELETE 和 INSERT 语句)。与快照读(Snapshot Read)不同,当前读会读取数据的最新版本,而不是事务开始时的历史版本。当前读可以确保事务在操作数据时获取到最新的数据,并对数据进行加锁以避免其他事务同时修改相同的数据,从而保证数据的一致性。

以下是一些使用当前读的 MySQL 语句示例:

  1. SELECT … FOR UPDATE 查询(对查询到的数据加写锁):

    SELECT * FROM users WHERE id = 1 FOR UPDATE;
    
  2. SELECT … LOCK IN SHARE MODE 查询(对查询到的数据加共享锁):

    SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
    
  3. 数据修改操作:

    UPDATE users SET age = 30 WHERE id = 1;
    DELETE FROM users WHERE id = 1;
    INSERT INTO users (id, name, age) VALUES (1, 'John', 30);
    

在 InnoDB 存储引擎中,当前读是通过以下步骤实现的:

  1. 当一个事务需要读取或修改某行数据时,InnoDB 会先检查该行数据是否被其他事务加锁。
  2. 如果该行数据未被加锁,InnoDB 会为当前事务加锁并读取该行数据的最新版本。如果该行数据已被加锁,当前事务会等待锁释放,然后再尝试加锁和读取数据。
  3. 当事务执行完毕并提交时,InnoDB 会释放所有的锁。

总之,当前读是一种用于加锁查询和数据修改操作的数据访问方式,它可以确保事务操作数据的一致性。与快照读不同,当前读会读取数据的最新版本并对数据进行加锁,以避免其他事务同时修改相同的数据。

  • 针对当前读(select … for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。

多版本并发控制(MVCC)

什么是MVCC

多版本并发控制(Multi-Version Concurrency Control,简称 MVCC)是一种用于实现事务隔离的技术。在 MySQL 中,InnoDB 存储引擎使用 MVCC 来实现事务的隔离性,提高数据库并发性能。

MVCC 的基本思想是为每个事务操作提供一个数据版本的快照,这样事务就可以在它自己的数据快照上独立运行,而不会受到其他并发事务的影响。通过这种方式,MVCC 可以在不使用加锁的情况下实现高效的事务隔离,从而提高系统的并发性能。

在 InnoDB 中,MVCC 是通过以下机制实现的:

  1. 每个事务都有一个唯一的事务标识(Transaction ID)。在事务开始时,InnoDB 会为其分配一个递增的事务标识。

  2. 对于每一行数据,InnoDB 都会保存一份额外的信息,包括创建该行数据的事务标识(创建版本号)和删除该行数据的事务标识(删除版本号,如果该行未被删除,则为空)。

  3. 当一个事务需要读取一行数据时,InnoDB 会根据事务标识和数据的创建版本号、删除版本号来判断该行数据是否对当前事务可见。如果该行数据对当前事务可见,事务将看到该行数据在它自己的快照中的状态;否则,事务将看不到该行数据。

  4. 当一个事务需要修改或删除一行数据时,InnoDB 会先检查该行数据是否对当前事务可见。如果可见,InnoDB 会创建一个新的数据行版本,并更新相应的版本号和事务标识,而不是直接修改原始数据行。这样,其他并发事务仍然可以看到原始数据行的状态,而不受当前事务的修改影响。

通过 MVCC,InnoDB 可以在保证事务隔离性的同时,提高数据库的并发性能。需要注意的是,MVCC 主要适用于读操作多于写操作的场景,因为在这种场景下,MVCC 可以避免大部分加锁开销。在写操作较多的场景下,MVCC 可能会导致更高的内存和存储开销,因为需要维护多个数据版本。

MVCC底层实现

在 MySQL 中,多版本并发控制(MVCC)是一种用于实现事务隔离的机制,它主要应用于 InnoDB 存储引擎。MVCC 允许多个事务同时访问数据库,而不会相互阻塞,从而提高了数据库的并发性能。MVCC 的底层实现依赖于以下几个关键组件:

  1. 事务 ID(Transaction ID):每当一个新事务开始,InnoDB 会为其分配一个唯一的递增的事务 ID。事务 ID 用于标识事务的先后顺序以及确定事务能访问哪些数据版本。

  2. 行版本(Row Version):在 InnoDB 中,每当一个事务修改一行数据,它会创建该行数据的一个新版本,而不是直接覆盖原始数据。新版本会包含修改后的数据以及修改事务的 ID。原始数据会保留在 undo 日志中,以便在事务失败或回滚时恢复数据。

  3. Read View:每当一个事务开始,InnoDB 会为其创建一个 Read View。Read View 包含一个事务 ID 列表,用于确定该事务能看到哪些数据版本。具体来说,一个事务只能看到:

    • 在其开始时已经提交的事务所做的更改。
    • 自己所做的更改。

    一个事务无法看到在其执行过程中其他事务所做的更改,从而避免脏读(Dirty Read)和不可重复读(Non-repeatable Read)问题。

  4. 一致性非锁定读(Consistent Non-locking Read):在默认情况下,InnoDB 使用一致性非锁定读策略。这意味着当一个事务读取数据时,它会根据 Read View 和行版本信息来读取符合条件的数据版本,而不会对数据加锁。这可以避免读-写和读-读之间的阻塞,从而提高数据库的并发性能。

  5. Undo 日志(Undo Log):在 InnoDB 中,undo 日志用于存储数据修改操作的历史记录,以便在事务失败或回滚时恢复数据。当一个事务开始时,InnoDB 会在 undo 日志中创建一个新的段(Segment)。当事务修改数据时,原始数据会被复制到该段中。当事务回滚时,InnoDB 会根据 undo 日志中的信息恢复数据。当事务提交时,InnoDB 会删除与该事务相关的 undo 日志。

总之,在 MySQL 的 InnoDB 存储引擎中,MVCC 的底层实现依赖于事务 ID、行版本、Read View、一致性非锁定读和 undo 日志等组件。通过使用这些组件,MVCC 可以实现事务隔离,允许多个事务同时访问数据库,而不会相互阻塞,从而提高了数据库的并发性能。

你可能感兴趣的:(MySQL,mysql,数据库)