理解 MySQL 中的脏读、不可重复读、幻读及其事务隔离级别

admin2024-08-20  9

在数据库系统中,事务(Transaction)是一个不可分割的操作序列。为了保证数据的一致性和完整性,数据库系统提供了事务的隔离机制。根据 SQL 标准,事务隔离级别分为四种:READ UNCOMMITTED(读未提交)、READ COMMITTED(读已提交)、REPEATABLE READ(可重复读)和 SERIALIZABLE(可串行化)。不同的隔离级别可以避免不同类型的数据不一致问题:脏读、不可重复读、幻读。

1. 脏读(Dirty Read)

脏读 是指一个事务能够读取到另一个事务未提交的数据。这可能导致一个事务读取到的数据是临时的、不正确的。

示例:

假设有一张 accounts 表,用于存储用户的账户信息。

CREATE TABLE accounts (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    balance DECIMAL(10, 2)
);

当前表中的数据如下:

idnamebalance
1Alice1000.00
  • 事务 A 开始并更新 Alice 的账户余额:

    START TRANSACTION;
    UPDATE accounts SET balance = 1500.00 WHERE id = 1;
    

    (此时事务 A 尚未提交)

  • 事务 B 开始,并读取 Alice 的账户余额:

    START TRANSACTION;
    SELECT balance FROM accounts WHERE id = 1;
    

    READ UNCOMMITTED 隔离级别下,事务 B 读取到的余额为 1500.00,即事务 A 尚未提交的修改。假设事务 A 随后回滚:

    ROLLBACK;
    

    此时事务 B 读取到的数据实际上是错误的,因为事务 A 已回滚,Alice 的余额仍然是 1000.00

避免脏读:设置隔离级别为 READ COMMITTED 或更高,确保事务只能读取到已提交的数据。

2. 不可重复读(Non-Repeatable Read)

不可重复读 发生在同一个事务中,读取同一条记录两次时,由于另一个事务对该记录进行了更新,导致两次读取的结果不一致。

示例:
  • 事务 A 开始并读取 Alice 的账户余额:

    START TRANSACTION;
    SELECT balance FROM accounts WHERE id = 1;
    

    第一次查询结果为 1000.00

  • 事务 B 更新了 Alice 的账户余额:

    START TRANSACTION;
    UPDATE accounts SET balance = 1500.00 WHERE id = 1;
    COMMIT;
    
  • 事务 A 再次读取 Alice 的账户余额:

    SELECT balance FROM accounts WHERE id = 1;
    

    这次查询结果变为 1500.00,与第一次查询结果不一致,导致不可重复读。

避免不可重复读:设置隔离级别为 REPEATABLE READSERIALIZABLE,确保同一事务中对同一记录的读取结果一致。

3. 幻读(Phantom Read)

幻读 发生在一个事务中,读取同一范围的记录两次时,由于另一个事务插入或删除了数据,导致两次读取的结果不一致。幻读与不可重复读的区别在于,幻读是针对数据集范围的变化,而不可重复读是针对单条记录的变化。

示例:

假设有一张 orders 表,存储用户的订单信息。

CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    amount DECIMAL(10, 2)
);

当前表中的数据如下:

iduser_idamount
11200.00
21150.00
32300.00
41500.00
52400.00
  • 事务 A 开始并读取用户 user_id = 1 的所有订单:

    START TRANSACTION;
    SELECT * FROM orders WHERE user_id = 1;
    

    第一次查询结果为:

    iduser_idamount
    11200.00
    21150.00
    41500.00
  • 事务 B 插入一条新订单:

    START TRANSACTION;
    INSERT INTO orders (id, user_id, amount) VALUES (6, 1, 100.00);
    COMMIT;
    
  • 事务 A 再次读取用户 user_id = 1 的订单:

    SELECT * FROM orders WHERE user_id = 1;
    

    这次查询结果为:

    iduser_idamount
    11200.00
    21150.00
    41500.00
    61100.00

    事务 A 发现比第一次查询多了一条记录,这就是幻读。

避免幻读:设置隔离级别为 SERIALIZABLE,在这种隔离级别下,事务会对读取的数据集加锁,防止其他事务插入或删除记录。

4. 事务隔离级别

MySQL 提供了四种事务隔离级别来控制不同类型的数据不一致问题:

  • READ UNCOMMITTED:最低的隔离级别,允许脏读、不可重复读和幻读的发生。
  • READ COMMITTED:允许不可重复读和幻读的发生,避免脏读。大多数数据库默认的隔离级别。
  • REPEATABLE READ:避免脏读和不可重复读,但可能会发生幻读。MySQL 的默认隔离级别。
  • SERIALIZABLE:最高的隔离级别,避免脏读、不可重复读和幻读,但性能开销最大。

结语

选择合适的事务隔离级别对应用程序的性能和数据一致性至关重要。在设计数据库事务时,需要根据业务需求权衡不同类型的数据不一致问题,以及它们对系统性能的影响。

通过理解和合理运用这些隔离级别,可以有效避免脏读、不可重复读和幻读,从而保障数据的正确性和系统的稳定性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明原文出处。如若内容造成侵权/违法违规/事实不符,请联系SD编程学习网:675289112@qq.com进行投诉反馈,一经查实,立即删除!