一、什么是事务
事务(Transaction) 是一组数据库操作的集合,这些操作要么全部执行成功,要么全部不执行。在MySQL中,事务确保了一组数据库操作的原子性,一致性,隔离性和持久性,简称 ACID 属性。
- 原子性 (Atomicity):原子性指事务中的所有操作要么全部执行成功,要么全部不执行。即事务不可分割,事务中的操作要么全部完成,要么由于某种原因全部回滚。
- 一致性 (Consistency):一致性确保事务开始前和结束后,数据库状态保持一致。事务执行过程中虽然数据库状态可能临时不一致,但最终状态必须符合预期约束。
- 隔离性 (Isolation):隔离性保证多个事务在并发执行时互不干扰,一个事务的执行不应影响其他事务。这防止了“脏读”、“不可重复读”和“幻读”等并发问题。
- 持久性 (Durability):持久性确保一旦事务提交,数据将被永久保存,即使系统发生崩溃,数据也不会丢失。MySQL的InnoDB引擎通过将事务记录到日志文件(如redo log)来实现持久性。
二、为什么要使用事务
使用事务的主要原因是为了保持数据的 一致性 和 完整性。在涉及多个步骤的数据库操作中,事务能保证这些操作要么全部成功,要么全体失败,避免了数据的部分更改情况。例如,银行转账操作就需要保证事务的原子性。
三、事务的优缺点
优点:
缺点
- 数据安全性:在复杂业务操作中确保数据的完整性和一致性。
- 错误处理:在多操作场景下简化错误的处理,出现错误时可安全回滚。
- 并发控制:通过隔离级别和锁机制有效控制并发性,避免数据冲突。
- 性能影响:事务处理会增加额外的系统开销,在高并发环境下可能影响效率。
- 死锁风险:当多个事务竞争相同的资源时,可能会出现死锁,需额外处理。
- 实现复杂:隔离级别、锁机制的引入增加了系统实现和维护的复杂性。
四、MySQL 事务的使用及主要操作示例
1. 启动事务和提交事务
在MySQL中,使用 START TRANSACTION
或 BEGIN
命令开启事务,使用 COMMIT
命令提交事务,将更改永久保存。
原始数据:
示例:假设在一个银行系统中进行简单的转账操作,将账户1中的100元转账到账户2。
-- 开启事务
START TRANSACTION;
-- 操作1:从账户1扣除100
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- 操作2:向账户2增加100
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
-- 提交事务
COMMIT;
在此示例中,如果 COMMIT
成功执行,账户1和账户2的变动将永久保存到数据库中
2. 回滚事务
如果事务中的操作未能成功执行,或发生错误时,可以使用 ROLLBACK
命令将所有更改撤销,返回到事务开始之前的状态。
示例:在转账的例子中,如果账户1余额不足,会导致事务失败,则可以使用回滚来撤销操作。
START TRANSACTION;
-- 操作1:检查账户1的余额是否足够
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1 AND balance >= 100;
-- 如果扣款不成功(账户1余额不足),则执行回滚
IF ROW_COUNT() = 0 THEN
ROLLBACK;
ELSE
-- 操作2:向账户2增加100
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
COMMIT;
END IF;
3. 设置事务的隔离级别
MySQL支持四种隔离级别,使用 SET TRANSACTION ISOLATION LEVEL
命令设置隔离级别,以控制事务之间的相互影响。隔离级别从低到高为:
- READ UNCOMMITTED:允许“脏读”,即读到未提交的数据。
- READ COMMITTED:只能读到已提交的数据,防止“脏读”。
- REPEATABLE READ:确保同一事务中的多次查询结果一致,防止“不可重复读”(MySQL默认)。
- SERIALIZABLE:最高隔离级别,完全隔离,避免“幻读”。
示例:在事务中设置隔离级别,确保在多次查询中获得一致的结果。
-- 设置事务隔离级别为 REPEATABLE READ
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 开始事务
START TRANSACTION;
-- 第一次查询
SELECT balance FROM accounts WHERE account_id = 1;
-- 其他事务对账户余额做出修改(在REPEATABLE READ下不会影响当前事务)
-- 第二次查询,结果与第一次相同
SELECT balance FROM accounts WHERE account_id = 1;
-- 提交事务
COMMIT;
在此隔离级别下,即使其他事务修改了 account_id = 1
的余额,当前事务中的两次查询结果仍然一致,避免了“不可重复读”。
4. 使用 SAVEPOINT
和 ROLLBACK TO SAVEPOINT
在复杂的事务操作中,可以使用 SAVEPOINT
创建子事务控制点。ROLLBACK TO SAVEPOINT
可以回滚到指定的保存点,从而避免回滚整个事务。
示例:假设在一个事务中进行多个更新操作,其中的某一步失败时,只回滚该步骤,而保留之前的操作。
START TRANSACTION;
-- 操作1:插入一条记录
INSERT INTO accounts (account_id, balance) VALUES (3, 500);
-- 创建保存点
SAVEPOINT savepoint1;
-- 操作2:尝试更新记录
UPDATE accounts SET balance = balance - 100 WHERE account_id = 3;
-- 模拟错误:试图对不存在的账户扣款
UPDATE accounts SET balance = balance / 0 WHERE account_id = 4;
-- 错误发生,回滚到保存点1
ROLLBACK TO SAVEPOINT savepoint1;
-- 继续执行其他操作
UPDATE accounts SET balance = balance + 50 WHERE account_id = 3;
-- 提交事务
COMMIT;
在此示例中,由于在操作3中发生了错误,ROLLBACK TO SAVEPOINT savepoint1
只撤销了从保存点1之后的操作,而保留了之前的插入操作。
5. 自动提交模式 (Auto-commit)
MySQL 默认情况下每条 SQL 语句都会自动提交,可以通过设置 AUTOCOMMIT
参数来控制是否自动提交事务。SET AUTOCOMMIT=0
关闭自动提交,从而手动控制事务的提交和回滚。
示例:关闭自动提交模式,手动控制事务。
-- 关闭自动提交
SET AUTOCOMMIT = 0;
-- 开始事务(由于关闭了自动提交,默认所有操作都在同一个事务中)
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
-- 提交事务
COMMIT;
-- 恢复自动提交
SET AUTOCOMMIT = 1;
在此示例中,通过关闭 AUTOCOMMIT
,所有操作都在同一个事务中执行,直到调用 COMMIT
提交更改。
6. 使用行锁(SELECT ... FOR UPDATE)
在事务中使用 FOR UPDATE
可以锁住查询的行,以防止其他事务对同一行的数据进行修改。InnoDB引擎支持行锁,防止并发事务发生冲突。
示例:使用 FOR UPDATE
锁住账户1的记录,确保其他事务无法修改余额,直到当前事务结束。
-- 开始事务
START TRANSACTION;
-- 查询并锁住行(SELECT ... FOR UPDATE)
SELECT balance FROM accounts WHERE account_id = 1 FOR UPDATE;
-- 执行更新操作
UPDATE accounts SET balance = balance - 200 WHERE account_id = 1;
-- 提交事务
COMMIT;
在此示例中,FOR UPDATE
锁住 account_id = 1
的行,防止其他事务同时操作该行数据,直到当前事务提交为止。
7.不同隔离级别的性能与安全
总结
MySQL 事务中的主要操作包括:
- 开启和提交事务:
START TRANSACTION
开启事务,COMMIT
提交事务。 - 回滚事务:
ROLLBACK
撤销事务中的所有操作。 - 设置事务隔离级别:控制事务的隔离性,防止并发问题。
- 使用
SAVEPOINT
和ROLLBACK TO SAVEPOINT
:在事务中创建保存点,并回滚到指定保存点。 - 自动提交模式:通过设置
AUTOCOMMIT=0
关闭自动提交,手动控制事务。 - 使用行锁:使用
SELECT ... FOR UPDATE
锁住记录,防止并发冲突。