|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?用户注册
×
MySQL InnoDB 存储引擎与事务原理
一、具体的问题
很多刚接触 MySQL 的开发,建表从没管过存储引擎,直到遇到"事务没回滚""行锁锁错行""断电后数据丢了"才回头补课。其实这几个现象的答案都指向同一个东西:你用的存储引擎是不是 InnoDB,以及你是否真的理解了事务的 ACID 是怎么落地的。本文把这两点一次说清,让你建表和下 SQL 时心里有数。
二、核心原理
1. 为什么默认是 InnoDB
InnoDB 支持事务、行级锁、外键和崩溃恢复(redo/undo 日志);而老的 MyISAM 只有表锁、无事务、断电易损。MySQL 5.5 起默认引擎就是 InnoDB。所以新表基本都该用 InnoDB,除非是纯只读、不在乎事务的日志表才考虑 MyISAM。
2. 事务的 ACID 靠什么实现
- 原子性(A):undo log 记录每行修改前的镜像,回滚时按它反向恢复。
- 一致性(C):由应用逻辑 + 约束(主键、唯一、外键)共同保证。
- 隔离性(I):靠锁 + MVCC 多版本并发控制,让读写互不阻塞。
- 持久性(D):redo log 先写(WAL),事务提交即保证落盘,断电也不丢已提交数据。
3. 四个隔离级别与典型现象
READ UNCOMMITTED(会脏读)> READ COMMITTED(不可重复读)> REPEATABLE READ(MySQL 默认,靠 GAP/Next-Key 锁防幻读)> SERIALIZABLE(串行化,最安全但性能最差)。绝大多数业务用默认的 RR 就够了。
4. MVCC 让你"读不加锁"
InnoDB 给每行维护隐藏的事务 id 和回滚指针,普通 SELECT 默认走"快照读"(ReadView),看到的是事务开始时已提交的数据,既不会被别人未提交的写阻塞,也不会阻塞别人的写。这正是高并发下读写互不影响的根本原因。只有 SELECT ... FOR UPDATE 这类"当前读"才会去抢锁。
三、实例参考(动手步骤)
下面用一张账户表演示"显式事务 + 回滚保障",并验证隔离级别。
- -- 1) 确认当前引擎与隔离级别
- SHOW VARIABLES LIKE 'default_storage_engine';
- SELECT @@transaction_isolation;
- -- 2) 显式建 InnoDB 表
- CREATE TABLE account (
- id INT PRIMARY KEY,
- name VARCHAR(20),
- balance INT
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- INSERT INTO account VALUES (1,'A',1000),(2,'B',1000);
- -- 3) 转账:要么都成功,要么都回滚
- START TRANSACTION;
- UPDATE account SET balance = balance - 100 WHERE id = 1;
- UPDATE account SET balance = balance + 100 WHERE id = 2;
- COMMIT; -- 若中途报错,执行 ROLLBACK; 而非 COMMIT
- -- 4) 在另一个会话用默认 RR 读取,看不到未提交改动
- -- (SESSION B)SELECT * FROM account; -- 仍显示 1000/1000,直到 SESSION A COMMIT
复制代码
关键提醒:START TRANSACTION 之后必须显式 COMMIT 或 ROLLBACK;当 autocommit=1 时单条 SQL 自带隐式提交,容易让人误以为"加了事务就生效",其实没包住多句。
四、一个常被忽略的坑:长事务拖垮整个库
曾有一次线上告警"锁等待超时",排查发现是一条报表查询忘了提交,事务开着跑了 40 分钟。它不但一直占着行锁,还让 undo 日志无法 purge,版本链越积越长,所有依赖老版本的快照读都跟着变慢。教训很直接:事务里绝不放 SELECT 之后 sleep、绝不做外部 HTTP 调用;查完立刻 COMMIT。用 SELECT * FROM information_schema.innodb_trx 能实时看到哪些事务跑了太久,是排查锁问题的第一抓手。
五、实操检查清单
- 建表显式写 ENGINE=InnoDB,别依赖默认(老库可能还是 MyISAM)。
- 涉及金额、库存、状态流转的操作,必须 START TRANSACTION ... COMMIT/ROLLBACK,异常分支要有 ROLLBACK。
- 长事务会拖住 undo 日志和行锁,尽量短小;绝不在事务里做外部 HTTP 调用或 sleep。
- 需要"可重复读"的业务用默认 RR;若出现幻读,再考虑 GAP/Next-Key 锁或升到 SERIALIZABLE。
- 普通查询用快照读即可,只有真正要锁住待改的行才用 SELECT ... FOR UPDATE。
- 定期 SHOW ENGINE INNODB STATUS 看锁等待与长事务,并用 innodb_trx 及时 kill 异常长事务。
- 断电恢复后靠 redo 保证已提交不丢、靠 undo 回滚未提交,无需手工修数据。
|
|