Skip to main content

5.数据库事务与并发控制

5.1 事务的概念

事务(Transaction) 是一组作为单个工作单元执行的操作。在数据库管理系统 (DBMS) 中,事务确保操作要么全部成功执行,要么在发生错误时全部回滚,保证数据的完整性和一致性。

5.1.1. 事务的四个关键特性

事务的四个关键特性称为ACID,包括:

  • 原子性(Atomicity):事务中的所有操作要么全部完成,要么全部不执行。即使系统出现故障,任何中途完成的操作都会回滚,确保没有部分完成的操作留下。

  • 一致性(Consistency):事务执行前后,数据库都应保持一致的状态。事务开始前的数据规则必须在事务结束后仍然适用。

  • 隔离性(Isolation):即使有多个事务同时执行,事务间也应彼此独立,且一个事务的执行不应影响另一个事务。隔离性可以通过设置不同的隔离级别来控制(见后文的隔离级别部分)。

  • 持久性(Durability):一旦事务提交,结果就会永久保存在数据库中,即使系统崩溃,数据也不会丢失。

5.1.2事务的操作

事务的操作包括:

  • 开始事务: 在 MySQL 中,可以使用 START TRANSACTION; 来明确声明一个事务的开始。

  • 提交事务: 当事务中的所有操作成功完成后,使用 COMMIT; 提交事务,确保所有的变更被保存到数据库中。

  • 回滚事务: 如果事务中发生了错误,或者希望撤销事务中的操作,可以使用 ROLLBACK; 回滚事务,将数据库恢复到事务开始前的状态。

示例:

START TRANSACTION;

UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;

COMMIT;

如果在过程中有错误发生,可以用 ROLLBACK; 来取消更改。

5.2 并发控制

在多用户、多事务环境中,并发控制是非常重要的。它可以防止事务间相互干扰,保证数据库的一致性和完整性。主要的并发控制技术有事务隔离级别和数据库锁机制。

5.2.1 事务的隔离级别

隔离级别 决定了一个事务在执行过程中如何看到其他未提交事务的操作。SQL标准定义了四种事务隔离级别,从低到高分别是:

1. READ UNCOMMITTED(读未提交): 在这种隔离级别下,一个事务可以读取到另一个事务还未提交的数据。这种情况称为 脏读(Dirty Read),意味着可能会读到无效或将被回滚的数据。这个级别的并发性能最好,但一致性最差。

-- 示例
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

2. READ COMMITTED(读已提交): 一个事务只能读取到另一个事务已经提交的数据。未提交的数据不会被读取,避免了脏读问题。但仍然可能发生 不可重复读(Non-repeatable Read),即一个事务在不同的时间两次读取相同的数据,结果却不同。

-- 示例
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

3. REPEATABLE READ(可重复读): 在事务的整个执行过程中,读取的每条记录保持一致,不受其他事务提交影响。这解决了不可重复读问题。但是,仍然可能发生 幻读(Phantom Read),即一个事务多次执行相同的查询,结果集的行数却可能不同(例如其他事务插入了新行)。

MySQL 的默认隔离级别就是 REPEATABLE READ。

-- 示例
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

4. SERIALIZABLE(可串行化): 这是最高级别的隔离性,事务完全串行执行,避免了所有并发问题(包括脏读、不可重复读和幻读)。但这种隔离级别的性能最差,通常只在需要极高数据一致性时使用。

-- 示例
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

5.2.2 数据库锁机制

锁机制用于保护数据库中的数据在多个事务并发访问时不会出现冲突。主要有两种类型的锁:

  1. 共享锁(Shared Lock,S 锁): 允许多个事务同时读取同一资源,但不允许修改。使用共享锁可以防止其他事务对该资源进行写操作,确保读取的一致性。

  2. 排他锁(Exclusive Lock,X 锁): 只有获得排他锁的事务可以读取和修改资源,其他事务既不能读取也不能修改。这种锁用于保护写操作,确保同一时间只有一个事务能修改资源。

示例: 在 MySQL 中,可以显式加锁:

SELECT * FROM accounts WHERE account_id = 1 FOR UPDATE;  -- 获得排他锁
SELECT * FROM accounts WHERE account_id = 1 LOCK IN SHARE MODE;  -- 获得共享锁

5.2.3 死锁与解决策略

死锁 是指两个或多个事务相互等待对方持有的锁,导致它们无法继续执行。举个简单的例子:

  • 事务 A 锁住了资源 1,等待资源 2;
  • 事务 B 锁住了资源 2,等待资源 1;
  • 由于双方都无法释放对方所需的锁,进入了无限等待状态,形成了死锁。

死锁解决策略:

  1. 超时机制: 设置事务等待的最大时间,当超过这个时间时,放弃等待并回滚事务。

  2. 死锁检测: 数据库系统可以定期检查是否存在死锁,并自动回滚其中一个事务,解开死锁。

MySQL 内部会自动检测死锁并回滚一个事务以解决问题,但开发者也应尽量避免在应用程序层面造成死锁。

5.3总结

  • 事务是保持数据库操作一致性的重要机制,具有 ACID 特性。
  • 隔离级别决定了事务如何看待并发操作,选择合适的隔离级别可以在性能和一致性之间取得平衡。
  • 锁机制用于管理并发操作,但可能引发死锁。理解并使用共享锁和排他锁可以帮助开发者避免并发冲突。