MySQL事务

掌握事务概念、ACID属性和事务控制语句

什么是事务?

事务是数据库操作的基本单位,是一组要么全部执行成功,要么全部不执行的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';

事务最佳实践

  • 保持事务尽可能短,减少锁定时间
  • 避免在事务中进行用户交互
  • 使用适当的隔离级别平衡一致性和性能
  • 在应用程序中正确处理事务失败
  • 使用保存点处理复杂事务
  • 避免在循环中提交事务(影响性能)
最佳实践:对于需要多个操作作为一个整体执行的场景,务必使用事务来保证数据的一致性。

下一步学习

掌握了事务概念后,您可以继续学习:

  • MySQL索引 - 学习如何创建和使用索引提高查询性能
  • MySQL视图 - 了解如何创建和使用数据库视图
  • 存储过程 - 学习创建和使用存储过程