Skip to content

事务和索引,是 MySQL 进阶的两扇门 —— 事务与索引入门 ​

属于 S1 MySQL 深入 · 基础篇第 4 章(基础篇收官,下一篇进入深入篇) 上一篇:表设计与约束范式 下一篇:存储引擎与 B+ 树

基础篇前三章讲的是"怎么操作 MySQL",这一章开始讲"MySQL 怎么保证正确和快"——两个贯穿面试的核心概念:事务(正确性)和索引(性能)。这一篇先把它们"是什么、为什么、怎么用"讲清楚,深入的实现原理(MVCC、B+ 树、锁)留给深入篇。


事务:把一堆操作绑成一个"要么全成、要么全败"的整体 ​

转账是个经典例子:从 A 扣 100、给 B 加 100,两步必须同时成功或同时失败。如果只成功一半,钱就凭空消失了。事务(Transaction)就是把多条 SQL 绑定成一个原子操作。

ACID 四性(背到条件反射) ​

特性含义通俗解释靠什么实现(深入篇展开)
A 原子性 Atomicity全部成功或全部回滚要么全做,要么全不做undo log(回滚日志)
C 一致性 Consistency事务前后数据都满足约束账永远平应用 + 数据库共同保证
I 隔离性 Isolation并发事务互不干扰各查各的,像串行锁 + MVCC
D 持久性 Durability提交后不丢断电也不丢redo log(重做日志)

一句话:原子性管"失败怎么办",持久性管"崩溃怎么办",隔离性管"并发怎么办",一致性是最终目标(前三个特性都是为它服务的)。

事务的用法 ​

sql
-- 显式开启事务(关闭自动提交)
START TRANSACTION;      -- 或 BEGIN;

UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;

COMMIT;                 -- 提交:全部生效(不可再回滚)
-- ROLLBACK;            -- 回滚:全部撤销

-- SAVEPOINT:事务内的"存档点",可以只回滚到某个点
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;
SAVEPOINT after_deduct;
UPDATE account SET balance = balance + 100 WHERE id = 2;
ROLLBACK TO SAVEPOINT after_deduct;   -- 只撤销第二步,保留第一步
COMMIT;

注意:MySQL 默认 autocommit = 1(每条语句自动提交,无法回滚)。写多步操作必须显式 START TRANSACTION。DDL(CREATE/ALTER/DROP)不能回滚(隐式提交)。

并发事务会出什么乱子 ​

两个事务同时操作同一份数据,会出现三类问题——这是理解隔离级别的入口,先记住定义:

问题定义通俗场景
脏读读到别人未提交的数据A 改了钱没提交,B 读到了,A 回滚 → B 读到废数据
不可重复读同一事务内两次读同一行,值变了别人 UPDATE 提交了,你两次读到不同余额
幻读同一事务内两次范围查询,行数变了别人 INSERT 提交了,多出来一行"凭空出现"

区分后两个的关键:不可重复读是"内容变了"(UPDATE),幻读是"多了一行"(INSERT)。

隔离级别:用"容忍度"换"性能" ​

数据库针对上面三个乱子,定义了四种隔离级别,从松到严:

隔离级别脏读不可重复读幻读一句话
读未提交(Read Uncommitted)可能可能可能啥都不防,性能最好
读已提交(Read Committed, RC)不会可能可能只能读到已提交的
可重复读(Repeatable Read, RR)不会不会基本解决MySQL 默认
串行化(Serializable)不会不会不会全加锁,慢到没朋友
sql
-- 查看/设置当前会话隔离级别
SELECT @@transaction_isolation;                    -- 8.0 查看(旧版 @@tx_isolation)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

为什么 MySQL 默认是 RR 而 Oracle 默认 RC? 这是历史包袱:MySQL 5.0 之前 binlog 只支持 statement 格式,如果默认 RC,主从复制会出现数据不一致(具体机制见深入篇《事务与 MVCC》)。RR 下 InnoDB 用 MVCC + next-key lock 把幻读也基本堵住了,所以"默认 RR"并不像教科书说的那么慢。

基础篇阶段,隔离级别记住这张表 + 三个乱子的定义即可,Read View、MVCC 实现细节在深入篇《事务与 MVCC》里彻底展开。

索引:让数据库"不用翻遍全表"的东西 ​

索引是什么 ​

索引是为加速查询而建立的、额外维护的有序数据结构,本质是"书的目录":不建索引,WHERE name = '张三' 要全表扫描(一行行比对);建了索引,先查目录定位到位置,再取数据。

  • 索引的代价:写放大——每次 INSERT/UPDATE/DELETE 都要同步维护索引结构,索引越多写越慢。
  • 所以"索引越多越好"是错的,索引是拿写入性能换查询性能。

索引有哪些类型 ​

类型底层结构特点
B+ 树索引(默认)B+ 树支持等值 + 范围 + 排序,最通用(深入篇重点)
哈希索引哈希表只支持等值,O(1) 快,但不支持范围;InnoDB 有自适应哈希索引
全文索引倒排索引关键词搜索(MATCH ... AGAINST),中文分词效果有限
空间索引R 树地理坐标(不常用)

什么时候该建索引(入门版判断) ​

该建:

  • 频繁出现在 WHERE 条件的列(高选择性列优先:如手机号、邮箱,而不是"性别")
  • 频繁用于 ORDER BY / GROUP BY / JOIN 关联的列
  • 联合查询的最左列

不该建:

  • 数据量很小的表(几千行,全表扫比索引还快)
  • 频繁更新的列(索引维护成本高)
  • 低选择性列(性别只有 0/1,索引区分度低,优化器会放弃)
  • 大文本/长字符串列(索引太大,必要时用前缀索引)
sql
-- 入门三连:建索引 / 看索引 / 删索引
CREATE INDEX idx_name ON users(name);
SHOW INDEX FROM users;
DROP INDEX idx_name ON users;

索引的"能不能用上"非常讲究:最左前缀、回表、覆盖索引、索引失效……这些是深入篇《索引与 SQL 优化》《慢查询优化实战》的主战场。基础篇只需要建立"索引=有序目录、有代价、要挑列"的直觉。


串起来 ​

这一篇你带走三样东西:事务 = 要么全成要么全败(ACID 四性 + START TRANSACTION/COMMIT/ROLLBACK);三个并发乱子(脏读/不可重复读/幻读,区分 UPDATE 和 INSERT);索引 = 用写放大换查询速度的有序目录。这些概念是进入深入篇的地图——接下来每一篇都会把这些"为什么"拆到实现层面。

下一篇正式进入 深入篇 · 存储引擎与 B+ 树:为什么千万行的表主键查询还是毫秒级?InnoDB 到底是怎么做到的?

持续学习,持续构建。