复习MySQL的正确姿势:一份从环境搭建到源码级理解的完整路线图
最近一段时间,陆陆续续帮好几个团队做过MySQL相关的技术支持和面试辅导,发现一个很普遍的问题:大家平时CRUD写得飞起,但一旦被问到“MySQL的隔离级别到底怎么实现的”、“间隙锁什么时候会触发”、“EXPLAIN里Using filesort意味着什么”,很多人就开始含糊了。说到底是知识太碎片化,全是零散的点,没有连成线。这篇帖子就是一份“复习MySQL”的完整路线图,覆盖环境安装、连接排错、核心机制(事务、索引、锁、存储过程)、性能调优、误操作恢复,以及面试高频题。不管你是准备跳槽面试、工作中遇到瓶颈想系统梳理,还是刚入行想搭一套扎实的知识框架,这篇文章都能给你一条可以直接照做的路径。
先说清楚一件事:复习MySQL不等于重学一遍MySQL。如果你已经有了日常使用的基础,你需要的是把知识结构化,把“知道怎么用”升级成“知道为什么这么设计”。这篇文章我尽量按这个思路来——不只告诉你步骤,更多的会讲清楚每一步背后的原理和取舍。
1. 复习前的总体规划:先画地图再上路
1.1 把MySQL知识拆成四个图层
复习最怕的就是没有章法,今天看索引明天看锁后天看日志,三天之后前面全忘了。我自己的习惯是先把知识拆成四个图层,然后一层一层去补。
第一层是应用层,也就是SQL的编写能力,包括增删改查、多表连接、子查询、排序分组、窗口函数,以及存储过程、触发器、事件调度这些编程性内容。这一层对应的是日常开发中最常用的能力,复习时主要靠刷题和写demo来唤醒手感。
第二层是架构与内核层,这是拉开差距的关键,包括事务与隔离级别、MVCC机制、各类索引的数据结构、锁的类型与加锁规则、redo log/undo log/binlog三种日志的协作流程。说真的,面试里90%的高区分度问题都出在这一层。
第三层是运维与稳定性层,包括安装部署、参数调优、慢SQL排查、连接池管理、备份恢复、主从复制。这一层是很多开发者的短板,但工作中出问题往往都在这一层。
第四层是扩展与生态层,包括分库分表方案、数据同步工具(比如Flink同步到ClickHouse)、异构存储适配(比如表结构自动转TDengine超级表)、ORM框架的底层交互逻辑等。
复习的时候按这个顺序来,每层补完用实际的实验来验证理解,比闷头看书高效得多。
1.2 版本差异必须搞清楚:5.7和8.0不是一回事
很多人在复习时不太注意版本,这是个隐患。当前生产环境中5.7和8.0并存,而且差异远不止“性能更好”这么简单。
| 对比项 | MySQL 5.7 | MySQL 8.0 |
|---|---|---|
| 默认字符集 | latin1 | utf8mb4 |
| 默认认证插件 | mysql_native_password | caching_sha2_password |
| 窗口函数 | 不支持 | 支持 |
| 公用表表达式CTE | 不支持 | 支持 |
| 隐藏索引 | 不支持 | 支持 |
| 原子DDL | 不支持 | 支持 |
| 默认排序规则 | 区分大小写不敏感(一般情况) | utf8mb4_0900_ai_ci |
这些差异不是背下来就完事,得理解背后的动机。比如8.0把默认字符集改成utf8mb4,是因为互联网应用要处理Emoji和生僻字,utf8mb4才是真正的“完整Unicode”。认证插件换成caching_sha2_password则是因为老的mysql_native_password安全强度不够,但这也带来了兼容性问题——很多老的客户端连接8.0会直接报Authentication plugin 'caching_sha2_password' cannot be loaded。
版本选择上也需要留意生命周期。5.7系列的最后一个版本是5.7.44,官方之后不再有新版本了;8.0系列里,像8.0.44属于创新版本,而8.4是LTS长期支持版本,适合生产环境选型。复习或者搭建新环境时,建议直接基于8.0或者8.4,不要在新项目里还用5.7的思路。
1.3 用一个可复现的实验环境打底
复习不动手等于白看。我建议准备一个可以随时重置的实验环境,方案按优先级排列:
- Docker方式最快:
docker run --name mysql8 -e MYSQL_ROOT_PASSWORD=yourpass -p 3306:3306 -d mysql:8.0,一条命令搞定。但注意Docker安装MySQL有几个常见的坑,后面我在问题排查章节里专门说。 - 本机解压版(Windows绿色版)适合想了解目录结构和初始化流程的人,下载zip包解压、初始化数据目录、注册服务,整个过程能帮你理解MySQL的启动链路。
- Linux下用RPM或yum安装适合模拟生产环境,特别是CentOS/RHEL系列。
我自己复习时是用Docker跑了一个8.0和一个5.7(用不同端口),再配合本机装一个DBeaver或Navicat做可视化操作。两个版本对比着用,“差异”这件事会记得非常牢。
2. 环境搭建与连接排错:装不上、连不上才是真正劝退点
2.1 安装过程中的实操步骤与常见坑
先讲Windows解压版安装。MySQL 8.0的zip包解压后,一定要先看目录下有没有my.ini配置文件,没有就自己建一个。最基本的配置要包含basedir、datadir和port,datadir指向的目录不能存在或者必须为空。然后以管理员身份运行命令提示符,依次执行:
mysqld --initialize-insecure注意:
--initialize-insecure会生成一个密码为空的root账号;如果直接--initialize则会生成一个随机密码,输出在数据目录的.err日志里。很多人第一次装完找不到初始密码,十有八九是用错了初始化参数。
初始化完成后注册服务:mysqld --install MySQL,然后net start mysql启动。如果这里报“服务无法启动”,最常见的原因有两个:一是my.ini里配置的目录路径写错了,MySQL起不来但没有把错误直接弹出来;二是datadir目录权限不对。排查时不要干瞪眼,直接看data目录下的.err日志文件,里面有明确的原因。
Linux下RPM安装相对顺滑一些:
rpm -ivh mysql-community-server-8.0.44-1.el7.x86_64.rpm systemctl start mysqld grep 'temporary password' /var/log/mysqld.log安装后的第一件事是改密码,因为MySQL 8.0的validate_password组件默认开启,简单密码根本设不上。ALTER USER 'root'@'localhost' IDENTIFIED BY '你的强密码';会要求密码同时包含大小写字母、数字和特殊字符。
2.2 SSL连接错误的坑和解决方案
连接报SSL错误是高频问题,尤其是客户端工具或者JDBC方式连MySQL 8.0时报SSL connection error: SSL_CTX_set_default_verify_paths failed或者类似提示。这个问题本质上有两层:第一层是客户端要求SSL验证但证书路径有问题,导致握手失败;第二层是某些老客户端根本不支持MySQL 8.0默认开启的SSL配置。
解决办法按场景分:
- JDBC连接时,在连接串后加
useSSL=false&allowPublicKeyRetrieval=true。allowPublicKeyRetrieval是因为8.0的caching_sha2_password插件在非SSL连接时需要通过RSA公钥交换密钥。 - 命令行或客户端工具连接时,加
--ssl-mode=DISABLED跳过SSL验证。 - 如果确实需要SSL,那就得把服务端的
ca.pem、server-cert.pem、server-key.pem配置正确,并在客户端指定--ssl-ca。
这里多说一句:不要一上来就禁SSL,要分场景判断。本机开发环境禁掉没问题,生产环境跨网络传输时该开还是要开。
2.3 客户端工具选型与驱动问题
很多人用Navicat,功能完善但需要破解。我在这里不聊破解的事情,只说一点:如果遇到连不上8.0的问题,先看是不是Navicat版本太老。早期版本的Navicat for MySQL对8.0的caching_sha2_password认证支持不完善,这属于工具兼容性问题而不是数据库问题。
DBeaver是免费替代,完全够用。但它有个常见问题——离线环境下下载驱动会失败,一直转圈。解决办法是手动下载官方驱动JAR,然后在DBeaver的“数据库驱动管理器”里添加本地JAR文件。另外如果你要用ODBC方式连接MySQL 8.0,需要下载并安装MySQL ODBC Driver 8.0,这个驱动依赖Microsoft Visual C++ 2015 Redistributable(14.0版本),很多程序员装完ODBC驱动一运行就报E0434352错误,缺的就是这个VC++运行库。
3. 核心机制复习:事务、索引、锁、存储过程
3.1 事务:隔离级别和MVCC是怎么配合的
事务这块复习的重点不是背ACID,而是理解InnoDB是怎么实现ACID的。这里我建议画一张图:事务执行时,数据页上的修改先写到buffer pool,同时生成undo log(用于回滚)和redo log(用于崩溃恢复),提交时redo log刷盘。这张图画完,ACID四个特性各自靠什么机制实现的就一目了然了。
隔离级别这块,默认是REPEATABLE READ。很多人不理解为什么MySQL的RR能避免幻读,答案是两种机制配合:快照读靠MVCC,当前读(SELECT ... FOR UPDATE、UPDATE、DELETE)靠间隙锁和临键锁。MVCC的核心是undo log版本链和ReadView:事务开始时生成ReadView,里面记录当前活跃事务ID列表,据此判断某个版本是否可见。理解了ReadView的生成时机,就理解了RC和RR的区别——RC每条语句生成新的ReadView,RR整个事务复用同一个ReadView。这也是为什么RR下快照读不会读到其他事务新提交的数据。
复习事务时我强烈建议做一个实验:开两个终端,分别开启事务,用SELECT ... FOR UPDATE去更新同一行,观察一个事务等待、另一个提交后锁释放的过程,再配合SHOW ENGINE INNODB STATUS\G查看锁等待信息。做过这个实验之后对锁的理解完全不一样。
3.2 索引:B+树、回表与最左前缀
索引永远是面试的重灾区,复习时至少要搞清楚这几个问题:
第一,为什么InnoDB用B+树而不是B树或者红黑树?答案要点是:B+树所有数据都在叶子节点,叶子节点之间通过双向链表连接,非常适合范围查询和排序;磁盘IO次数取决于树高,三层B+树大概能存2000万行以上数据。这个问题答得好不好,直接反映一个人是背课文还是在真的理解数据结构。
第二,聚簇索引和二级索引的区别。InnoDB里主键索引就是聚簇索引,叶子节点存储整行数据;二级索引的叶子节点存储的是主键值,所以通过二级索引查询需要“回表”再查一次聚簇索引。如果查询的列在二级索引里都包含了,就不需要回表,这就是覆盖索引优化的原理。比如SELECT id, name FROM user WHERE name = '张三'且(name, id)是联合索引,那么直接走这个二级索引就能返回,避免回表。
第三,联合索引的最左前缀原则。(a, b, c)联合索引,能用到索引的查询条件是a、a+b、a+b+c。原因是B+树先按a排序、相同a内再按b排序、相同ab内再按c排序。理解了排序规则就理解了为什么跳过a直接查b用不上索引。
创建索引(CREATE INDEX idx_name ON table(col))的实操建议是:控制索引数量(单表建议不要超过5-6个),避免在低选择度列上建索引(比如性别字段只有两个值),字符串列可以用前缀索引col(10)来减小索引体积,但注意这会让排序和覆盖索引功能打折扣。
还有一个常被忽略的点:ORDER BY和索引的关系。排序字段如果正好走索引顺序,就用不到filesort;如果SQL里是ORDER BY a DESC LIMIT 10但索引是(a ASC),MySQL 8.0可以反向扫描索引,而5.7在特定场景下会触发filesort。EXPLAIN里看到Using filesort,基本意味着排序没走索引,需要重点优化。
3.3 锁的分类与死锁排查
锁的分类如果只背名词没有用,我建议按这个逻辑线来复习:
- 锁的粒度:表锁(DDL、
LOCK TABLES)、行锁(InnoDB特有,也分为共享锁S和排他锁X)。 - Record Lock(记录锁):锁住具体的索引记录。
- Gap Lock(间隙锁):锁住记录之间的间隙,防止其他事务在这个间隙插入数据,用来解决幻读。注意它是开区间,比如锁住(5, 10)这个区间不包含10。
- Next-Key Lock(临键锁):记录锁+间隙锁的组合,左开右闭区间(5, 10]。这是InnoDB RR级别下默认的加锁方式。
- 意向锁:表级别的意向共享锁/意向排他锁,用来快速判断表里是否已有行锁,避免逐行检查。
死锁这块,我见过太多现场了。经典场景是:事务A先锁了行1再锁行2,事务B先锁了行2再锁行1,两边互相等,形成一个环。InnoDB会通过检测机制选一个代价小的事务回滚,另一个正常执行。排查死锁唯一的正解是先SHOW ENGINE INNODB STATUS\G看LATEST DETECTED DEADLOCK段,里面有完整的事务和加锁信息,然后根据SQL反推业务逻辑,统一加锁顺序。
还有一个“锁表”的坑:一张大表在RR隔离级别下做UPDATE不带WHERE条件或者WHERE条件没走索引,InnoDB会因为找不到要锁的精确记录而行锁升级到表锁(实际是锁了大量记录),表现就是整个表不可写,其他会话全部阻塞。遇到这种“锁表”先别急着重启,用SELECT * FROM information_schema.innodb_trx\G看事务状态,找到持锁事务ID,再KILL对应连接,或者等锁事务提交。
3.4 存储过程:业务逻辑下沉的正反两面
存储过程这个知识点,很多人觉得过时了,但实际上金融、报表场景用得还是很多,且有它不可替代的位置:逻辑直接跑在数据库进程内,省去应用层和数据库之间的多次网络交互,对于单条SQL无法完成的批量逻辑很有优势。
复习存储过程要会写基本的语法结构:
DELIMITER $$ CREATE PROCEDURE sp_test() BEGIN DECLARE v_cnt INT DEFAULT 0; DECLARE v_id INT; DECLARE cur CURSOR FOR SELECT id FROM t WHERE status = 0; DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_cnt = 1; OPEN cur; read_loop: LOOP FETCH cur INTO v_id; IF v_cnt = 1 THEN LEAVE read_loop; END IF; UPDATE t SET status = 1 WHERE id = v_id; END LOOP; CLOSE cur; END$$ DELIMITER ;存储过程最容易出问题的点是错误处理。默认情况下如果存储过程中某条语句报错,整个过程直接终止,且没有明确提示。很多人在存储过程里写业务逻辑,发现“看起来没执行完,也没有报错”,其实是被CONTINUE HANDLER吞掉了错误。调试经验是:在过程里加入DECLARE EXIT HANDLER FOR SQLEXCEPTION捕获异常,并用GET DIAGNOSTICS获取错误代码和消息,把错误信息写进日志表,或者直接用SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '自定义错误信息';主动抛出。
存储过程的适用边界也要想清楚:逻辑涉及多表事务且不需要频繁变更时,用存储过程是合适的;一旦逻辑频繁迭代、需要版本管理,存储过程就是噩梦,因为Git对存储过程的diff非常不友好。面试时如果能讲清楚这个边界,比单纯背诵语法加分很多。
4. 性能调优与运维:从慢SQL到误操作恢复
4.1 连接池:为什么应用不能直连MySQL
数据库连接池是容易被忽视但线上影响巨大的一个环节。每次新建MySQL连接都要经历TCP握手、认证、权限校验,高并发场景下这个开销非常可观。连接池的作用就是把连接复用起来,避免频繁建连。SELECT ... FROM ... WHERE跑得再快,如果连接一直在建立和销毁,整体性能也会被拖下水。
连接池要考虑的参数有:initialSize(初始连接数)、maxActive(最大活跃连接数)、maxWait(获取连接超时时间)、minIdle(最小空闲连接数)。常见问题是连接数设置过大——一个应用200个连接,10个应用就是2000个连接,而MySQL默认的max_connections是151,直接打爆。另一个问题是连接泄漏:应用从池里取了连接但没释放(没有finally或者try-with-resources),池被耗尽,表现为“获取连接超时”。排查时用SHOW PROCESSLIST看连接状态,很多Sleep连接一直不释放,多半就是泄漏。
4.2 慢SQL分析:EXPLAIN是你最好的老师
遇到线上慢查询,第一件事不是改代码,而是拿到慢SQL,加EXPLAIN分析执行计划。关键字段逐个过一遍:
type:从好到差依次是system、const、eq_ref、ref、range、index、ALL。看到ALL就是全表扫描,基本可以认定有问题。key:实际使用的索引。有时候MySQL没用上你建的索引,原因可能是统计信息没更新(跑一下ANALYZE TABLE),或者查询条件里对索引列做了函数运算(比如WHERE DATE(create_time) = '2024-01-01'),导致索引失效。rows:预估扫描行数,和实际行数差距太大说明统计信息不准。Extra:出现Using filesort、Using temporary、Using index,前两个是优化重点,后者是好事(说明走了覆盖索引)。
额外的优化手段:对某些SQL,FORCE INDEX指定走某个索引是下策,上策是搞清楚为什么优化器选了错误的索引——往往是统计信息过期或者直方图缺失。MySQL 8.0支持直方图,ANALYZE TABLE t UPDATE HISTOGRAM ON col;可以给优化器提供更准确的分布信息。
4.3 修改表结构、默认值和SQL_MODE
复习时很容易忽略的一个点是SQL_MODE。sql_mode直接决定SQL的松紧程度。比如默认的STRICT_TRANS_TABLES模式下,插入超长字符串或者非法的日期会直接报错;如果把它关掉,MySQL会截断数据或者插入0000-00-00并产生告警。这就是“mysql设置默认值为0”的需求背景——很多老系统的表结构习惯用DEFAULT '0'而不是NULL,这其实是设计妥协,新表建议直接用DEFAULT 0配合正确的字段类型。
修改表结构这块要特别小心。ALTER TABLE ADD COLUMN在5.7之前会锁表,5.7虽然引入了online DDL也不是所有操作都全程online。8.0的原子DDL解决的是DDL过程中异常中断导致数据字典不一致的问题,但大表加字段依然可能导致长时间元数据锁,阻塞线上写入。适用于大表的方案是pt-online-schema-change(Percona Toolkit),通过触发器+分批拷贝的方式实现无锁在线变更。
4.4 误操作数据恢复:UPDATE没带WHERE怎么办
“mysql update 还原”是特别容易搜到的关键词,说明很多人踩过这个坑。如果真的发生了一次没有WHERE条件的UPDATE t SET status = 1,不要慌,恢复思路按顺序走:
- 如果表是InnoDB且binlog是开启状态(
log_bin=ON),可以利用binlog做时间点恢复。先确认故障时间点:SHOW BINARY LOGS; SHOW BINLOG EVENTS IN 'binlog.0000xx';,然后恢复目标库的备份到误操作前一刻(如果没有备份,这步没法做),再通过binlog把备份时刻之后、误操作之前的正常操作回放进去,跳过误操作那一条。 - 如果binlog没开,也没备份,那基本只能靠运维的定期快照(比如云数据库的自动备份)恢复。
- 有一种“半还原”的技巧:如果误操作是全表
UPDATE,但表里大部分行的值其实没变,可以用备份业务表,对比找出被改动的行,手工修正。
从根上讲,这类事故的防御要点是两条:生产环境的binlog必须开,且binlog_format用ROW模式(而不是STATEMENT),这样能记录每行变更的前后镜像,恢复精度更高。另外执行高危操作前先SELECT看一眼范围,再把这句SELECT改成UPDATE,并且先把影响行数查出来。
4.5 常见问题速查表
| 问题现象 | 常见原因 | 排查/处理建议 |
|---|---|---|
net start mysql服务无法启动 | 配置路径错误、datadir无权限、端口被占用 | 查看data目录下.err日志;用mysqld --console前台启动看错误 |
| 客户端连不上、报SSL错误 | 客户端/驱动不支持或SSL证书验证失败 | JDBC加useSSL=false&allowPublicKeyRetrieval=true;命令行加--ssl-mode=DISABLED |
| 安装8.0后不知道初始密码 | --initialize生成了随机密码 | 查data目录.err日志;或者用--initialize-insecure生成空密码 |
| Docker安装MySQL失败 | 镜像没下载下来、端口冲突、容器内目录未挂载 | 检查docker logs;用docker run -p 3307:3306换端口;挂载目录设权限 |
| DBeaver驱动一直下载失败 | 离线环境/网络限制 | 手动下载官方驱动JAR,在驱动管理里添加本地文件 |
| 表被锁死,DML全部阻塞 | 大事务未提交、长查询持锁 | SELECT * FROM information_schema.innodb_trx定位事务,KILL连接 |
| 表结构修改卡住 | 元数据锁等待 | SHOW PROCESSLIST查看State为Waiting for table metadata lock的会话,等待其结束或KILL |
5. 面试高频题与生态扩展:复习的最终检测
5.1 高频面试题清单与答题方向
如果复习时间有限,这张表里的题建议优先准备:
| 面试题 | 考察点 | 关键答题角度 |
|---|---|---|
| 为什么用B+树做索引 | 数据结构 | 有序性+低树高+范围查询友好+叶子节点链表 |
| InnoDB和MyISAM的区别 | 存储引擎 | 事务/行锁/外键/崩溃恢复/聚簇索引 |
| RR隔离级别为什么没幻读 | 事务+锁 | 快照读走MVCC,当前读走间隙锁 |
| 什么情况下索引会失效 | 索引 | 函数运算、隐式类型转换、LIKE前导通配符、违反最左前缀 |
| 什么是回表 | 索引 | 二级索引查到主键,再查聚簇索引;覆盖索引可以避免 |
| 死锁怎么排查 | 锁 | show engine innodb status + 统一加锁顺序 |
| 一条UPDATE是怎么执行的 | 内核链路 | 连接-解析-优化-执行-加锁-修改buffer pool-写redo/undo-提交 |
| 慢SQL怎么优化 | 调优 | EXPLAIN看type/key/rows,是否filesort/temporary |
| 主从延迟怎么解决 | 架构 | 并行复制、读写分离架构、大事务拆分 |
| binlog有几种格式 | 日志 | STATEMENT/ROW/MIXED,生产推荐ROW |
这些题不能只背标准答案,每个问题背后都能延伸到实际场景。比如“一条UPDATE的执行链路”,完全可以结合你实际Debug过的死锁案例来讲,面试官最怕的就是听到背诵腔,最想看到的就是有真实经验的表达。
5.2 数据同步与异构存储场景
这个部分对工作两三年之后的开发者越来越重要。一个典型的场景是:核心业务数据在MySQL,但分析类需求需要把数据实时同步到ClickHouse。常用的方案有Flink CDC:部署Flink CDC Connector监听MySQL的binlog,把变更事件解析成DataStream,再写入ClickHouse。关键点是MySQL要开启binlog且格式为ROW,Flink CDC底层对binlog的解析依赖debezium,所以如果表结构里字段类型特殊(比如JSON、DECIMAL),要注意CDC的类型映射问题。
另一个场景是MySQL表结构自动转TDengine超级表和子表。TDengine作为时序数据库,建模思路完全不同:超级表(STable)类似模板,子表是具体设备的数据流。从MySQL迁移时,需要把业务表打散成“标签字段+时序字段”,不能用MySQL的建表语句直接跑。网上有现成的转换工具,但最好理解清楚原理再迁移——标签是静态信息(设备ID、型号),量测值是动态变化的时间序列(温度、电压),转换的难点就是识别这两类字段。
还有一个和ETL相关的高频问题:Sqoop从Hive导数据到MySQL时连接不上。大多数原因是MySQL驱动JAR没放在Sqoop的lib目录,或者连接串里没指定useSSL=false(Sqoop 1.4.6+默认连接MySQL会走SSL校验,经常报错)。这种问题本质上是生态工具的兼容性排查,复习时多做几次完整的同步流程,对理解各个组件的边界非常有帮助。
5.3 复习方法和节奏建议
最后说点实在的:我自己复习MySQL的节奏是一周左右。前三到四天按图层体系把原理过一遍,每天配合写10条左右SQL验证理解;第五天开始刷题,重点是执行计划分析和锁等待案例分析;第六天把面试高频题自己讲一遍,最好开录音,回听有没有含糊的地方;第七天留给自己,专门把复习中发现的知识盲区补齐。
实操上有三个小建议:
- 不要只看书或者只看视频,一定要动起来。每学一个机制就去命令行实验一遍,MySQL的输出就是最好的老师。
- 建一个自己的错误日志文档,记录每次踩坑的报错信息、原因和解决方案。这种东西在面试里讲出来比任何标准答案都有说服力。
- 复习期间把
information_schema、performance_schema和sys三个系统库过一遍,很多线上问题排查都要靠它们。
我个人实际带人复习时还有一个心得:找一个人,互相给对方出题,把知识点用自己的话讲出来。能给别人讲明白,才是真正复习到位了。这种输出倒逼输入的方法,比一个人闷头刷资料效率高一倍不止。
MySQL这个知识体系值得反复回炉,每次复习都会发现自己之前理解的盲区。这篇文章给出的路线和实验方法都是反复走过之后沉淀下来的,按这个思路走一轮,你对MySQL的认识应该能比碎片化学习阶段扎实不少。