什么是事务?
事务是数据库操作的基本单位,是一组要么全部执行成功,要么全部不执行的SQL语句集合。事务确保数据库从一个一致性状态转换到另一个一致性状态。
事务的ACID属性
- 原子性(Atomicity) - 事务中的所有操作要么全部完成,要么全部不完成
- 一致性(Consistency) - 事务执行前后,数据库必须保持一致性状态
- 隔离性(Isolation) - 多个事务并发执行时,彼此之间相互隔离
- 持久性(Durability) - 事务提交后,对数据库的修改是永久性的
事务控制语句
开始事务
SQL示例
START TRANSACTION;
-- 或者
BEGIN;
提交事务
SQL示例
COMMIT;
回滚事务
SQL示例
ROLLBACK;
设置保存点
SQL示例
SAVEPOINT savepoint_name;
回滚到保存点
SQL示例
ROLLBACK TO savepoint_name;
事务示例
银行转账事务
SQL示例
START TRANSACTION;
-- 从账户A扣款
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- 向账户B存款
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
-- 如果两个操作都成功,提交事务
COMMIT;
注意:如果在事务执行过程中发生错误,可以使用ROLLBACK回滚所有操作,确保数据的一致性。
SAVEPOINT详细示例
SQL示例
START TRANSACTION;
INSERT INTO orders (order_id, amount) VALUES (1, 100);
SAVEPOINT after_insert;
UPDATE inventory SET stock = stock - 1 WHERE product_id = 10;
ROLLBACK TO after_insert; -- 撤销UPDATE,保留INSERT
COMMIT;
事务隔离级别
MySQL支持四种事务隔离级别:
| 隔离级别 | 描述 | 可能的问题 |
|---|---|---|
| READ UNCOMMITTED | 读取未提交数据 | 脏读、不可重复读、幻读 |
| READ COMMITTED | 读取已提交数据 | 不可重复读、幻读 |
| REPEATABLE READ | 可重复读(MySQL默认) | 幻读 |
| SERIALIZABLE | 串行化 | 无并发问题,但性能最低 |
隔离级别实现原理
MySQL InnoDB使用MVCC(多版本并发控制)和锁机制实现隔离级别:
- READ COMMITTED:每次读取生成新的快照
- REPEATABLE READ:事务内使用同一快照,通过间隙锁防止幻读
设置隔离级别
SQL示例
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
事务中的锁机制
InnoDB支持行级锁和表级锁:
SQL示例
-- 行锁:SELECT ... FOR UPDATE
START TRANSACTION;
SELECT * FROM accounts WHERE account_id = 1 FOR UPDATE;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
COMMIT;
-- 共享锁:LOCK IN SHARE MODE
SELECT * FROM products WHERE id = 10 LOCK IN SHARE MODE;
死锁检测与解决
MySQL会自动检测死锁并回滚其中一个事务。可以通过以下方式减少死锁:
- 以固定顺序访问表和行
- 保持事务简短
- 使用较低的隔离级别
- 为表添加合理的索引
SQL示例
-- 查看最近死锁信息
SHOW ENGINE INNODB STATUS;
-- 设置死锁等待超时时间(秒)
SET innodb_lock_wait_timeout = 50;
自动提交模式
MySQL默认启用自动提交模式,每个SQL语句都会自动提交。可以关闭自动提交来手动控制事务:
SQL示例
-- 关闭自动提交
SET autocommit = 0;
-- 开启自动提交
SET autocommit = 1;
并发问题
当多个事务并发执行时,可能会出现以下问题:
- 脏读 - 读取到另一个事务未提交的数据
- 不可重复读 - 同一事务中多次读取同一数据,结果不一致
- 幻读 - 同一事务中多次查询,返回的行数不一致
并发问题示例
SQL示例
-- 事务A
START TRANSACTION;
SELECT * FROM users WHERE id = 1; -- 读取数据
-- 事务B
START TRANSACTION;
UPDATE users SET name = '新名字' WHERE id = 1;
-- 事务A再次读取同一数据,如果隔离级别低,可能会看到不一致的数据
SELECT * FROM users WHERE id = 1;
长事务的危害及避免
长事务会占用锁资源、增加死锁风险、导致UNDO日志膨胀。避免方法:
- 将大事务拆分为多个小事务
- 避免在事务中进行用户交互或远程调用
- 使用应用程序逻辑控制事务边界
- 监控长时间运行的事务:
SELECT * FROM information_schema.INNODB_TRX WHERE trx_state='RUNNING'
XA事务(分布式事务)
MySQL支持XA协议,用于跨多个数据库的分布式事务:
SQL示例
XA START 'xid1';
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
XA END 'xid1';
XA PREPARE 'xid1';
XA COMMIT 'xid1';
事务最佳实践
- 保持事务尽可能短,减少锁定时间
- 避免在事务中进行用户交互
- 使用适当的隔离级别平衡一致性和性能
- 在应用程序中正确处理事务失败
- 使用保存点处理复杂事务
- 避免在循环中提交事务(影响性能)
最佳实践:对于需要多个操作作为一个整体执行的场景,务必使用事务来保证数据的一致性。
下一步学习
掌握了事务概念后,您可以继续学习: