MySQL学习笔记:存储引擎、索引与触发器的底层原理与实战
2026/9/24 20:21:04 网站建设 项目流程

最近有个朋友跟我聊起 MySQL 学习,他问我:每天刷 SQL 题基本都能答上来,但一到面试问原理就心里没底,尤其是存储引擎、索引、触发器这几块,总感觉会写但讲不透。我特别理解这种感觉,因为我自己学到 DAY09 这个阶段时,正好卡在同样的位置上——光知道CREATE INDEX能加速、CREATE TRIGGER能自动执行,但完全说不清它们内部是怎么配合的。

DAY09 我把这三块放在一起啃,是有原因的。存储引擎决定了数据怎么存、怎么锁、怎么恢复,是 MySQL 的“地基”;索引决定了数据怎么被快速找出来,是“查得快”的关键;触发器则是把一部分业务逻辑下沉到数据库里,让它在特定事件发生时自动响应。这三者表面上看是三个独立知识点,实际上环环相扣:索引的结构取决于存储引擎的实现方式,触发器又必须依附于某张具体的数据表,而表最终由存储引擎来管理。学完这一轮我最大的感受是,零散地记命令和系统地把它们串成一条线,效果完全不一样。

这篇文章就把我 DAY09 的学习过程整理下来,重点是把我踩过的坑、想通的逻辑和可以直接抄走的代码示例都放进来。如果你是正在学 MySQL 的开发者,或者是准备面试想系统过一遍存储引擎、索引、触发器的人,这篇笔记应该能帮你省不少时间。

1. 存储引擎入门:为什么 InnoDB 成了默认选项

存储引擎这个词第一次听会觉得有点抽象,说白了它决定了 MySQL 在底层怎么组织数据、怎么处理并发、怎么保证数据不丢。MySQL 从 5.5.5 版本开始把 InnoDB 设为默认引擎,这一选就是十几年,不是没有理由的。

1.1 InnoDB 和 MyISAM 的正面 PK

我在学习时最先做的就是对比 InnoDB 和 MyISAM,因为早期接触 MySQL 的人一定绕过不这两个高频主角。直接把关键差异列出来,可能比任何解释都直观。

对比维度InnoDBMyISAM
事务支持支持 ACID 事务不支持
锁粒度行级锁表级锁
外键约束支持不支持
聚簇索引是,主键直接决定物理存储顺序否,数据和索引分开存放
全文索引MySQL 5.6 前不支持,5.6 后支持原生支持
崩溃恢复通过 redo log 实现崩溃恢复无类似机制,崩溃后易丢数据
适用场景高并发 OLTP、交易系统只读查询、数据仓库、日志类表

学习的时候我特意给自己的练习库建了一张 MyISAM 表,又建了一张 InnoDB 表,然后开启两个事务去改同一条记录。结果 InnoDB 这边第二个事务会等锁,而 MyISAM 那边直接整张表锁住,影响范围明显大得多。行锁和表锁的差距,只有实际触发过才知道什么叫“并发瓶颈”。

1.2 InnoDB 三大看家本领:事务、行锁、崩溃恢复

InnoDB 能成为默认引擎,核心是它同时握住了三个对业务系统至关重要的能力。

第一个是事务。你可以把事务理解成“要么全做,要么全不做”的一组 SQL。转账就是个最经典的场景,扣钱和加钱必须同时成功或同时失败,不能扣了钱对方却没收到。InnoDB 通过 undo log 记录回滚前的数据状态,支持 ROLLBACK,也通过锁机制保证并发事务之间的隔离。MySQL 八股文里常问的 ACID,就是在说:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。这四个特性不是天生就有的,而是 InnoDB 用日志和锁机制一起堆出来的。

第二个是行级锁。InnoDB 的锁针对的是索引记录,不是整张表。这意味着在高并发场景下,两个事务同时修改同一张表里的不同记录时,彼此不会互相阻塞。这一点在订单系统、库存系统这种写频繁的场景里尤其重要,MyISAM 的表锁在这种场景下基本会被打爆。

第三个是崩溃恢复。数据库不可能永远不宕机,关键是宕机后怎么处理。InnoDB 的 redo log 好比记账草稿——每次提交事务时先把变更顺序写入 redo log,数据文件可以慢慢刷盘,但日志必须先落盘。如果服务器突然断电,重启后 InnoDB 会靠 redo log 把还没刷进数据文件的变更重新应用一遍,保证已经提交的事务不丢。这也是 InnoDB 和 MyISAM 在“数据可靠性”上最大的差别。

1.3 其他引擎在什么场合下有用

InnoDB 很强,不代表其他引擎就该被遗忘。学习这些冷门引擎不是凑数,而是为了遇到特定场景时能知道还有别的选择。

  • MyISAM:现在业务系统里已经很少用了,但如果你维护的是纯只读的统计报表表、历史归档表,不在乎事务,读的性能依然不错。
  • Memory:数据全部存在内存里,读写极快,但 MySQL 重启后数据全部清空。适合放会话信息、临时中间结果这类丢失了也不心疼的数据。
  • Archive:只支持 INSERT 和 SELECT,不支持 UPDATE、DELETE,数据会被高比例压缩。适合存日志流水这种只增不改的冷数据。
  • CSV:直接把数据存成 CSV 文件,方便外部程序处理,但没有任何索引能力,只适合做数据交换。

学习时我犯过一个认知错误:以为换存储引擎是一件很随意的事。实际上ALTER TABLE ... ENGINE=InnoDB这条命令在数据量大的线上表上执行时会锁表,重建索引的代价非常高。真实项目里应该在建表时就根据业务定好引擎,而不是上线后再去改。这一点希望大家不用像我一样踩过才知道。

2. 索引:从 B+ 树结构看懂索引为什么快

索引是很多人学 MySQL 时最熟悉的陌生人。平时知道给 where 条件后的字段加索引,但问到为什么索引能快、B+ 树和 B 树有什么区别、回表是什么,常常就说不清楚了。

我从一个最简单的问题开始:一张有 1000 万行数据的表,不加索引时查一条记录要遍历整张表,最多可能要比较 1000 万次;加上主键索引之后,走的是一棵 B+ 树,从根节点走到叶子节点大约只需要 3 到 4 次磁盘 IO。这个差距不是“快了一点点”,而是天壤之别。

2.1 B+ 树为什么比 B 树更适合 MySQL

教材上会说 B 树和 B+ 树都是多路平衡查找树,但实际数据库中 B+ 树完胜。两者最大的差异在于:B 树每个节点都会存数据,而 B+ 树只有叶子节点存数据,非叶子节点只存储索引键和指向子节点的指针。

这个差异带来了三个直接影响:

第一,B+ 树的非叶子节点不存数据,所以一个节点能容纳的索引键数量就更多,树的层数更矮。经典的 InnoDB 页大小是 16KB,假设主键是 BIGINT 类型,一个非叶子节点大概能放下上千个索引键。3 层 B+ 树就能存储上千万条数据。树越矮,磁盘 IO 次数越少,查询自然越快。

第二,B+ 树的叶子节点通过链表串联,非常适合范围查询。你在 SQL 里写WHERE id > 100 AND id < 500时,找到 100 之后沿着叶子节点的链表顺序往下读就行。B 树要做中序遍历,代价高得多。

第三,B+ 树的数据存得集中,磁盘预读能发挥作用。数据库底层读取磁盘时通常按页读取,一页里包含多条相邻记录,顺序扫描效率比 B 树那种分散存储好得多。

哈希索引也顺便提一句。哈希索引结构上是键值对,等值匹配(WHERE id = 100)的速度其实可以做到 O(1),比 B+ 树还快。但它完全无法支持范围查询和排序,所以 MySQL 默认不采用,只在 Memory 引擎里可以看到真正意义上的哈希索引,InnoDB 里的自适应哈希索引也只是缓存层的优化手段。

2.2 聚簇索引、二级索引与回表

InnoDB 中,索引和数据是放在一棵 B+ 树里的,这个树就是聚簇索引(clustered index)。你可以把它理解为“索引即数据,数据即索引”。每个 InnoDB 表只能有一个聚簇索引,主键就是它的检索入口;如果你建表时没定义主键,InnoDB 会找第一个非空的唯一索引,再找不到就生成一个隐藏的 rowid 作为主键。

二级索引(也叫辅助索引或非聚簇索引)则是另外单独建立的 B+ 树,它的叶子节点不存储完整的行数据,而是存储主键值。这也是为什么我学到这里时,突然明白了“尽量使用主键查询”这句话的真正含义——用二级索引查询时,InnoDB 先在二级索引树中找到符合条件的主键值,然后再到聚簇索引中按主键去取整行数据。这个过程叫回表(table lookup)。

回表不是必然发生的。如果查询的字段恰好全部包含在二级索引里,比如索引是(username, age),你查的是SELECT username, age FROM user WHERE username = '张三',那么二级索引的叶子节点上已经有这两个字段了,不需要回表,这种情况称为覆盖索引(covering index)。覆盖索引是日常优化查询的重要手段,能省一次聚簇索引的查找,在高频查询上收益特别明显。

2.3 联合索引的最左前缀原则

联合索引是另一个容易懵的地方。给(a, b, c)建联合索引,等于同时建了(a)(a, b)(a, b, c)三个索引,这就是所谓的最左前缀原则。我最初一直以为联合索引是把三个字段都建索引,直到实际验证才发现理解错了。

举个例子:

CREATE INDEX idx_abc ON t_order (user_id, status, create_time);

这个联合索引生效的查询组合有:

  • WHERE user_id = 100
  • WHERE user_id = 100 AND status = 1
  • WHERE user_id = 100 AND status = 1 AND create_time > '2024-01-01'

不生效的组合有:

  • WHERE status = 1
  • WHERE create_time > '2024-01-01'
  • WHERE status = 1 AND create_time > '2024-01-01'

原因在于 B+ 树的索引键是按顺序排序的:先按 user_id 排,相同 user_id 再按 status 排,再相同再按 create_time 排。跳过了前导字段,后面的字段在索引里根本无法确定比较范围。为了验证,我建了一张 20 万行的测试表,分别用带status不带user_id的查询看执行计划,果然 type 变成了 ALL,走了全表扫描。

你在业务设计里经常遇到一个误区:为了“覆盖所有查询条件”,把常用字段都塞进索引。实际上字段越多,索引占用的空间越大,写入时维护索引的代价也越高。合理的做法是分析常用的最左前缀组合,而不是把每个查询都揣一遍。

3. 索引建了还是慢:8 个典型的索引失效场景

这是 DAY09 里我最想分享的部分。因为单单“会建索引”不算会,能在实际业务里判断出“为什么没走索引”才是真本事。我总结了 8 个真实踩过或排查过的场景,每个都值得盯一眼。

3.1 隐式类型转换和函数操作

隐式类型转换是我踩过次数最多的坑。想象一下phone字段在表里是 varchar 类型,但代码里传参时写成了整数类型:

SELECT * FROM user WHERE phone = 13800138000;

MySQL 会自动把字符串类型的 phone 转成数字去比较,等于对索引列执行了类型转换,索引直接失效。验证方法也简单,用EXPLAIN看执行计划的 type 列,如果从 ref 变成 ALL,就是没走索引。解决办法是让传入参数保持和字段类型一致,电话这类身份证号、手机号建议统一用字符串存。

函数操作同理。WHERE DATE(create_time) = '2024-01-01'这样写,相当于对索引列做了函数计算,索引键的顺序被打乱,MySQL 只能全表扫描。正确写法是改成范围查询:

SELECT * FROM t_order WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02';

这样 create_time 本身没有被包裹在函数里,才能正常走索引。

3.2 LIKE 模糊查询和 OR 条件

LIKE的关键在于通配符的位置。WHERE name LIKE '%张%'因为%在最前面,无法从索引树头部开始定位,索引失效;WHERE name LIKE '张%'则是可行的,因为 MySQL 能在 B+ 树里根据前缀定位。如果业务真的需要“包含”式搜索,建议上全文索引或用搜索引擎,而不是靠 SQL 硬憋。

OR 条件也是一个高频杀手。比如:

SELECT * FROM t_order WHERE user_id = 100 OR status = 1;

即使 user_id 和 status 分别建了单列索引,MySQL 也可能选择全表扫描,因为 OR 只要一个条件成立就得返回行,导致优化器很难直接利用 B+ 树的结构做快速定位。实践中可以把 OR 改写成 UNION ALL,或者评估是否真的需要这种查询。

3.3 其他几个容易忽略的失效场景

剩下的几个场景,我用一张表快速记录,大家可以直接拿来当排查清单:

场景原因分析建议
对索引列使用!=<>InnoDB B+树无法从“不等于”中快速定位区间改为><的联合条件
IS NOT NULLIS NULL使用不当索引列大量为 NULL 时优化器判断走索引不划算给字段设置 NOT NULL 默认值
联合索引未遵循最左前缀缺少前导字段,后续字段无序可比调整查询条件顺序或修改索引字段顺序
使用OR连接非索引列无法对所有分支都高效利用索引改用 UNION ALL
对索引列做表达式运算表达式打乱索引键顺序将表达式移到等号右侧
字符串列查询时数字与字符串类型混用隐式类型转换参数类型与字段类型保持一致

排查方法统一推荐:跑一句EXPLAINkeyrows两列。key为 NULL 就是没用索引,rows估算扫描行数远超预期往往也是索引没生效的信号。在开发环境用真实数据量测试,别拿几十条数据的小表做判断,数据量太小的时候优化器确实会认为全表扫更快,这是正常现象,不代表索引没用。

4. 触发器:把业务逻辑塞进数据库的权衡

如果说索引是帮 MySQL“读得快”,那触发器就是让 MySQL“自动干活”。它的意思很简单:在某个表上发生 INSERT、UPDATE、DELETE 事件时,自动执行你预先定义好的 SQL 语句。听起来像灵丹妙药,但用起来牵扯不少权衡问题。

4.1 触发器语法与 NEW / OLD 的使用

MySQL 创建触发器的基本语法:

CREATE TRIGGER trigger_name {BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name FOR EACH ROW BEGIN -- 在这里写触发后要执行的逻辑 END;

时间点有两类:BEFORE 表示在操作前执行,AFTER 表示在操作后执行。事件类型对应三种操作。FOR EACH ROW表示行级触发,即每一行受影响时都会执行一次。

触发器里最核心的两个关键词是NEWOLD

  • NEW代表新插入或更新后的那一行数据
  • OLD代表更新或删除前的那一行数据

INSERT 只有 NEW,DELETE 只有 OLD,UPDATE 同时有 NEW 和 OLD。比如我们想记录订单金额变更前后的差异,可以这样写:

CREATE TRIGGER trg_order_amount_log AFTER UPDATE ON t_order FOR EACH ROW BEGIN INSERT INTO t_order_amount_log(order_id, old_amount, new_amount, change_time) VALUES (NEW.id, OLD.amount, NEW.amount, NOW()); END;

这样每次t_order表的 amount 被更新后,都会自动在日志表里插入一条记录。你不用改业务代码,不用提心吊胆地担心漏记,触发器自己就会完成。

4.2 三个经典的触发器实战场景

第一个场景是审计日志。金融、电商系统里任何核心表的数据变更都要留痕。用触发器做审计日志的好处是逻辑集中,无论你从哪里发起 UPDATE,都会被记录下来,不会因为某个接口忘记写日志而漏掉。

第二个场景是级联更新。比如商品表和商品销量统计表,业务上下单后要更新统计表的销量。如果每次都在下单逻辑里手动更新,容易忘,尤其是从多个入口下单时。用触发器在t_order表 INSERT 后统一更新统计表,逻辑就集中在数据库一侧。

第三个场景是数据完整性校验。比如工单系统规定,完结状态的工单不能被再次修改。用 BEFORE UPDATE 触发器直接抛出一个错误:

CREATE TRIGGER trg_prevent_modify_finished_order BEFORE UPDATE ON t_work_order FOR EACH ROW BEGIN IF OLD.status = 'FINISHED' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已完结工单不允许修改'; END IF; END;

这样哪怕有人绕过服务端直接连数据库改数据,也会被数据库层面拦住。这种控制力是只在应用层做校验无法达到的。

4.3 触发器的坑,每一个我都踩过

触发器看着方便,但用不好就是给自己埋雷。我总结几条个人体会最深的。

第一,隐形逻辑会造成“惊喜”。尤其是团队协作时,应用开发者往往不知道表上有触发器,改了数据后莫名多出日志、多改了别的表,排查时懵很久。这个问题的解决方案是:触发器命名规范、代码评审时把触发器清单列出来、在数据库设计文档里注明。

第二,处理性能问题。触发器默认在同一个事务里执行,如果触发逻辑写得很重,等于每次操作都要额外承担一份开销。在高并发写入场景下,触发器里的每一次 UPDATE 或 INSERT 都会拉长主操作的事务时间,隐患相当明显。我见过有人把复杂的汇总计算写进触发器,结果导入一批数据时慢到无法接受,最后改成定时任务才解决。

第三,递归触发风险。A 表的触发器去更新 B 表,B 表的触发器又回来更新 A 表,如果设置不当,可能形成无限循环。虽然可以通过sql_mode和触发机制控制,但排查起来非常痛苦。我个人的建议是尽量让触发器单向执行,不要反向更新回源表。

第四,调试困难。普通 SQL 可以直接执行看结果,触发器是“事件驱动”,难以单步调试。当触发器逻辑出错,往往到数据出问题时才能发现。实话说,触发器只在逻辑简单、变化不频繁的场景下才真正划算;一旦逻辑变复杂,可以优先考虑放到服务端代码里实现。

5. 学习日记实战:订单、库存和审计日志的一次完整设计

前几节分别拆开了看,这里我把存储引擎、索引、触发器三块知识拼到一个完整的业务场景里。用模拟的订单系统来当例子,你会看到它们是怎么配合的。

5.1 建表与索引设计

假设我们有两张核心表:商品表t_product和订单表t_order。商品表存储当前库存,订单表记录每一笔下单明细。

CREATE TABLE t_product ( product_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '商品ID', product_name VARCHAR(64) NOT NULL COMMENT '商品名称', stock_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '剩余库存', version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (product_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表'; CREATE TABLE t_order ( order_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '订单ID', order_no VARCHAR(32) NOT NULL COMMENT '订单编号', product_id BIGINT NOT NULL COMMENT '商品ID', quantity INT UNSIGNED NOT NULL COMMENT '购买数量', amount DECIMAL(10,2) NOT NULL COMMENT '订单金额', user_id BIGINT NOT NULL COMMENT '用户ID', status TINYINT NOT NULL DEFAULT 0 COMMENT '订单状态: 0已创建 1已支付 2已取消', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (order_id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_status (user_id, status), KEY idx_product_id (product_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

这里几个细节是想清楚了的:订单号用唯一索引uk_order_no保证不重复;高频查询是“查某用户某状态下的订单”,所以建联合索引idx_user_status;订单表只用 InnoDB,因为下单是典型的 OLTP 写操作,事务和行锁缺一不可。t_product表里留了一个 version 字段,是为了在应用层做乐观锁,防止并发下单时库存被超卖。

5.2 用触发器做库存扣减

在真实系统里,下单时扣减库存通常放在一个数据库事务里完成。但为了演示触发器的作用,我们可以设计一个更直接的场景:当订单表插入一条新记录时,自动扣减商品表里对应商品的库存。

CREATE TRIGGER trg_order_after_insert AFTER INSERT ON t_order FOR EACH ROW BEGIN UPDATE t_product SET stock_count = stock_count - NEW.quantity WHERE product_id = NEW.product_id; END;

这个触发器在每次订单生成后自动扣库存。逻辑简单,用来演示很有代表性。但必须指出,真实电商系统往往不会只靠触发器扣库存,因为还要考虑库存不足回滚、分布式场景下的可靠性等因素。触发器的价值在于“单库内的强一致”,一旦跨服务、跨数据库,就要引入分布式事务方案了。

5.3 用触发器做审计日志

继续完善这个场景,订单表修改时留审计日志。我们建一张订单操作日志表,并且加一个 AFTER UPDATE 触发器,记录订单状态变化。

CREATE TABLE t_order_log ( log_id BIGINT NOT NULL AUTO_INCREMENT, order_id BIGINT NOT NULL, from_status TINYINT NOT NULL, to_status TINYINT NOT NULL, operate_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (log_id), KEY idx_order_id (order_id) ) ENGINE=InnoDB COMMENT='订单状态变更日志'; CREATE TRIGGER trg_order_status_log AFTER UPDATE ON t_order FOR EACH ROW BEGIN IF OLD.status != NEW.status THEN INSERT INTO t_order_log(order_id, from_status, to_status) VALUES (NEW.order_id, OLD.status, NEW.status); END IF; END;

这里加了一个IF OLD.status != NEW.status的判断,避免订单金额等其他字段被更新时也写一条无意义的日志。这样的细节,正是实际开发中优化触发器性能的关键思路——能从源头减少无效数据写入。

5.4 这一套设计里值得反复琢磨的点

整个设计看起来顺理成章,但有个问题值得琢磨:触发器和索引竟然也有关联。订单表里建立了idx_product_id,所以在订单表插入时 MySQL 需要维护这个二级索引树;触发器更新库存时,t_product表的主键索引也要跟着更新。索引越多,写入时的维护成本越高,这正好呼应了前面说的“不要堆索引”。一个表里 3 到 5 个索引往往是性价比最高的范围,过多反而拖慢写入。

另外,触发器执行失败时,默认会导致主操作回滚。也就是说trg_order_after_insert里库存更新失败,INSERT INTO t_order也会失败。这在不经意间保证了订单和库存的一致性,属于触发器的隐藏性质,理解这一点对排查线上问题非常有帮助。

6. 面试题速查:存储引擎、索引、触发器怎么答

DAY09 学习清单里,面试对应试是绕不开的。我把今天学的三块内容整理成一份速查表,方便后续复习时一眼看清答题骨架。

常见面试题答题要点
InnoDB 和 MyISAM 的区别事务、行锁 vs 表锁、外键、聚簇索引、崩溃恢复能力
为什么 InnoDB 使用 B+ 树而不是 B 树或哈希B+ 树非叶子节点不存数据、层数矮、磁盘 IO 少、叶子节点链表支持范围查询
什么是聚簇索引,什么是二级索引聚簇索引叶子节点存完整行,二级索引叶子节点存主键
什么是回表使用二级索引查到主键后,再到聚簇索引取整行
覆盖索引是什么查询字段全部包含在索引中,不需要回表
联合索引最左前缀原则B+ 树按索引字段从左到右排序,跳过前导字段无法高效定位
哪些情况会导致索引失效模糊匹配前缀%、隐式类型转换、函数操作、OR 连接非索引列、!=
触发器有哪些使用场景审计日志、级联更新、数据完整性校验、防止非法操作
触发器的缺点隐形逻辑、性能损耗、调试困难、可能递归触发
存储过程、触发器、事件三者区别存储过程手动调用,触发器自动触发,事件按时间调度

对于前端的同学可能没接触过数据库管理,我想强调一句:面试时不需要把每个机制都背到源码级别,但必须要能画出一条逻辑链。比如面试官问索引为什么快,你能答出“数据量大的时候遍历太慢,B+ 树把查找次数压缩到树高级别的磁盘 IO,所以快”,这就够用了。难点恰恰在于把知识连接起来,而不是背孤立的知识点。

7. DAY09 的最后一公里:把这套知识真正用起来

到这里,存储引擎、索引、触发器的基础知识基本过了一遍。但“学完”和“会用”之间还有一条不小的沟。我把 DAY09 实操过程中觉得最有价值的验证方法分享出来。

动手建一张至少 100 万行的测试表,分别试下全表扫描、主键查询、二级索引查询、联合索引查询,每次都用EXPLAIN看执行计划。真实数据量下的体验,远远比背几条规则来得扎实。比如你会看到在没有索引时EXPLAINrows直接等于表里的总行数,而加了索引后下降到几十行,直观感受到为什么一切优化都要围绕索引展开。

触发器一定要亲手写一个带SIGNAL报错的 BEFORE UPDATE 例子,体会下“数据库拒绝请求”是什么感觉。然后用一个日志表验证 AFTER INSERT 的自动写入。我的经验是:代码写得再熟,没有实际执行一遍,还是很难理解 NEW 和 OLD 在不同事件里到底保留了哪一侧的数据。

这一天的学习从“背概念”开始,到“连成一条线”结束。我现在再看一张建表语句时,脑子里会自动浮现出存储引擎决定行级锁粒度、主键索引决定数据物理排序、联合索引影响高频查询路径、触发器帮我在数据库层兜底的数据流画面。提升学习效率最实在的方式,不是多看几篇博客,而是让知识点之间形成互动——希望这份 DAY09 学习笔记也能帮你建立起同样的一根线。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询