
很多人一说学 MySQL第一反应就是去搜“安装教程”装完跑起来能查数据就觉得会了。但真到工作里你会发现那些让你头疼的问题往往不是语法不会而是不知道数据库在背后替你做了什么。比如为什么改个表结构就把线上卡死了为什么加了索引还是慢为什么一个事务没提交就把整张表锁住了。这篇总结我就按自己这几年实际用 MySQL 的路线来写从概念、安装、日常操作到事务锁、索引、排障调优把基础知识串成一条线。1. 先搞清楚 MySQL 是干什么的表、行、SQL 分类这些地基1.1 关系型数据库到底解决什么问题MySQL 是关系型数据库管理系统核心模型是“表”。你可以把一张表想象成 Excel 的一个 sheet每一行是一条记录每一列是一个字段。但和 Excel 最大的区别是数据库要解决三件事并发访问、数据一致性、高效检索。这一点想明白了后面学事务、锁、索引的时候就不会觉得是孤立的知识点。关系型数据库强调“关系”也就是表与表之间通过主键外键关联。比如订单表里存 user_id用户表里也有 id通过这个字段把两张表连起来查询。这一套设计来自关系代数MySQL 只是把它落地成 SQL 语言。很多人直接跳进增删改查从来不理解为什么要有范式设计、为什么要拆表等遇到数据冗余和更新异常时再回头补课代价就大了。1.2 SQL 的四大类别只停留在 selectSQL 分为四大类这是基础中的基础但面试和工作里很多人只对查询有印象DDL数据定义语言CREATE、ALTER、DROP、TRUNCATE操作的是表结构。DML数据操作语言INSERT、UPDATE、DELETE操作的是数据内容。DQL数据查询语言SELECT最常用。DCL数据控制语言GRANT、REVOKE控制权限。日常开发 80% 的精力花在 DQL 上但真正容易出事故的往往是 DDL 和 DML。比如 ALTER TABLE 在大表上执行时会消耗大量 IO甚至阻塞读写UPDATE 不带 WHERE 条件会全表更新。分清这四类你在心里就会有个安全边界什么操作轻、什么操作重、什么操作需要审批。1.3 存储引擎和字符集两个容易被忽略的地基MySQL 的存储引擎是插件式的最常用的是 InnoDB 和 MyISAM。从 5.5 开始 InnoDB 就是默认引擎它支持事务、行级锁、崩溃恢复这是选它的核心理由。MyISAM 不支持事务、用的是表级锁、崩溃后容易损坏现在基本只在读多写少的历史库里见到。你可以用 SHOW ENGINE INNODB STATUS 看引擎运行状态也可以用 SHOW TABLE STATUS 查看某张表用的引擎。字符集更是个隐蔽的坑。强烈建议所有表统一用utf8mb4而不是 utf8。utf8 在 MySQL 里最多存 3 字节连 emoji 和生僻字都存不了utf8mb4 是 utf8 的超集兼容性最好。建库时指定一次后面省掉无数“问号乱码”的麻烦CREATE DATABASE mydb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;排序规则同样值得注意utf8mb4_unicode_ci 按 Unicode 规则排序utf8mb4_general_ci 速度快但规则稍微粗糙。中文业务场景两者差别不大但一旦涉及多语言unicode_ci 更稳。2. 装一个能用的 MySQLWindows、Linux、Docker 三条路怎么选2.1 Windowszip 解压安装的完整流程Windows 上安装 MySQL 有两类主流方案msi 安装包和 zip 压缩包。msi 图形化点下一步就行但如果你想要可控性我推荐 zip 解压方式整个过程反而能帮你理解 MySQL 的目录结构。下载对应版本 zip 包后解压到比如 D:\mysql-8.0.40-winx64然后在根目录新建 my.ini[mysqld] basedirD:/mysql-8.0.40-winx64 datadirD:/mysql-8.0.40-winx64/data port3306 character-set-serverutf8mb4 [client] default-character-setutf8mb4注意 datadir 这个目录不用手动建用管理员身份打开 cmd依次执行# 初始化数据目录生成无密码的 root加 --initialize-insecure mysqld --initialize-insecure # 注册为 Windows 服务 mysqld --install MySQL80 # 启动服务 net start mysql这里有个高频报错net start mysql 提示服务无法启动。绝大多数原因是 my.ini 里的 basedir、datadir 路径写错或者 data 目录权限不对、3306 端口被占用。排查顺序是先看 MySQL 自己的错误日志datadir 下的 .err 文件再检查端口netstat -ano | findstr 3306最后确认 my.ini 不是 UTF-8 带 BOM 的格式——带 BOM 会导致配置解析失败这个坑特别隐蔽。2.2 Linuxrpm 安装 5.7 与 8.0 的差异Linux 上最常见的两种安装方式yum 官方仓库源和 rpm 包手动装。CentOS 上很多人用 rpm 方式核心是装三个包mysql-community-common、mysql-community-libs、mysql-community-server按顺序装否则依赖会报错。rpm -ivh mysql-community-common-5.7.44-1.el7.x86_64.rpm rpm -ivh mysql-community-libs-5.7.44-1.el7.x86_64.rpm rpm -ivh mysql-community-server-5.7.44-1.el7.x86_64.rpm装完启动systemctl start mysqld # 5.7 会生成临时密码 grep temporary password /var/log/mysqld.log这里提醒一句5.7.44 是 MySQL 5.7 系列的最后一个版本官方在 5.7.43 之后直接把 5.7.44 作为收尾版本发布以后 5.7 不会再更新。如果你现在还在新项目里用 5.7建议直接上 8.0因为 8.0 在字符集、性能、安全方面变化很大。比如 8.0 默认认证插件是 caching_sha2_password很多旧客户端连接时会报认证失败5.7 默认是 mysql_native_password。这些细节在排障章节我会专门说。2.3 Docker容器化部署的版本、数据卷与常见失败Docker 装 MySQL 是现在团队环境里最省事的方案一条命令就能拉起来docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e MYSQL_DATABASEtestdb \ -v mysql_data:/var/lib/mysql \ mysql:8.0关键点有三个第一必须挂数据卷把容器的 /var/lib/mysql 持久化到宿主机否则容器一删数据全没第二时区最好显式指定 -e TZAsia/Shanghai第三如果想用 docker compose 管理配置文件长这样services: mysql: image: mysql:8.0 container_name: mysql8 ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: 123456 volumes: - mysql_data:/var/lib/mysql volumes: mysql_data:常见失败场景我也列一下docker pull 的时候报failed to decode referrers index: invalid这类错误多半是 docker 客户端和 buildx 插件版本过旧先把 Docker Desktop 更新到较新版本在 ARM 机器上拉镜像时如果默认拉不到可以指定平台docker pull --platform linux/arm64 mysql:8.0。另外宿主机 3306 端口如果已经被本机 MySQL 占了容器会启动失败换 3307 映射即可。访问容器内的 MySQL要么通过映射端口用宿主机 IP 连要么进容器里执行docker exec -it mysql8 mysql -uroot -p1234562.4 为什么我建议新手先装一个再说很多人在“装哪个版本”“用哪种方式”上纠结很久其实没必要。基础知识不落到实处全是空中楼阁。我的建议是Windows 用户直接下 zip 包走一遍解压安装流程有 Linux 环境就 rpm 走一遍Docker 环境就 docker run 跑一遍。三种方式都装过一次你对 basedir、datadir、服务注册、端口、数据卷这些概念就有了肌肉记忆后面解决任何环境问题都不慌。3. 日常操作里的高频细节排序、去重、默认值与改表结构3.1 order by 排序多字段与 limit 的配合排序是查询里出现频率很高的功能但很多人只停留在ORDER BY col这一层。实际业务里最常用的组合是多字段排序加分页SELECT id, name, age FROM user WHERE status 1 ORDER BY age DESC, id ASC LIMIT 20;这里有两个细节一是 DESC 只作用于紧挨着它的那个字段ORDER BY age, id DESC的意思其实是 age 升序、id 降序这是最容易写错的地方二是排序字段最好带上索引否则数据量大时 MySQL 会在内存或磁盘上做 filesort性能会明显下降。可以用 EXPLAIN 看 Extra 列如果出现 Using filesort就要考虑优化排序字段或加索引。排序还有中文场景的坑如果表字段排序规则是 utf8mb4_general_ci中文排序是按拼音还是按编码顺序取决于 collation。想要稳定的拼音排序推荐用 gbk 或者显式指定排序规则比如ORDER BY name COLLATE utf8mb4_unicode_ci。3.2 distinct 与 or 去重别被“去重”带偏去重是另一个高频需求。SELECT DISTINCT name FROM user很好理解但它是对整行去重不是说你只查 name 字段就只针对 name 去重。如果想查“每个用户的最新一条记录”DISTINCT 是做不到的得用窗口函数或者 GROUP BY-- 窗口函数取每个用户最新一条 SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY create_time DESC) rn FROM order_log ) t WHERE t.rn 1;关于网络热词里提到的“mysql 的 or 能去重吗”答案很明确OR 本身不去重它是逻辑条件表示满足任意条件即可。很多人以为WHERE a1 OR b1会像 UNION 一样去重实际上如果一行数据同时满足 a1 和 b1它只会被返回一次这是 SQL 集合语义决定的不是“去重”的效果。想真正去重要么加 DISTINCT要么用 UNION 替代SELECT name FROM user WHERE a 1 UNION SELECT name FROM user WHERE b 1;UNION 自带去重如果想保留重复行用 UNION ALL。这一点在面试里经常被拿来区分候选人是否真的理解集合运算。3.3 设置默认值为 0 的细节“MySQL 设置默认值为 0”是搜索热词说明很多人卡在建表。语法非常简单CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, age INT NOT NULL DEFAULT 0, score DECIMAL(10,2) DEFAULT 0.00 );但这里有几个容易踩的坑。第一MySQL 8.0 的严格模式默认开启下如果给 TIMESTAMP 类型设置 DEFAULT 0可能报错或行为不符合预期TIMESTAMP 有特殊的自动更新特性建议DEFAULT CURRENT_TIMESTAMP。第二ALTER TABLE 加默认值时要分两步还是合并成一步取决于你是否想立刻生效-- 添加带默认值的新列 ALTER TABLE t_user ADD COLUMN level INT NOT NULL DEFAULT 0;第三默认值只对“插入时不指定该列”的情况生效。如果你显式插入 NULL只要字段允许 NULL默认值不会自动填入。3.4 alter table 改表结构别在高峰期直接干数据库修改结构对应 DDL包括添加列、修改字段类型、改字段名、删除列。基本的写法-- 加列 ALTER TABLE t_user ADD COLUMN addr VARCHAR(200) DEFAULT ; -- 修改字段类型 ALTER TABLE t_user MODIFY COLUMN addr VARCHAR(500); -- 改名改类型 ALTER TABLE t_user CHANGE COLUMN addr address VARCHAR(500); -- 删列 ALTER TABLE t_user DROP COLUMN address;重点提醒在大表上执行 ALTER TABLE 会锁表并复制数据InnoDB 里很多 DDL 在 5.6 之后引入了 Online DDL但即便如此加字段、改类型这类操作在百万、千万行级别上也可能产生长时间阻塞。我的习惯是先看表大小SHOW TABLE STATUS超过千万行的表结构变更必须走低峰期窗口或使用 gh-ost、pt-online-schema-change 这类工具。这是生产环境里最容易“把人搞没”的操作千万不要觉得语法会了就随便执行。3.5 update 误操作如何还原“mysql update 还原”也是高频热词。大部分 UPDATE 误操作是 WHERE 条件漏写导致的我给你的建议分三层第一层代码层面强制执行前先 SELECT 一遍确认影响行数第二层事务内执行确认无误再 COMMITSTART TRANSACTION; UPDATE t_user SET status 1 WHERE id 123; -- 先查一下不对就 ROLLBACK; ROLLBACK;第三层万一已经 COMMIT 了唯一的还原手段是 binlog。前提是开启 binlog 且格式为 ROW这样才能通过 mysqlbinlog 解析出原始镜像做反向 SQL。生产库强烈建议设置binlog_formatROW虽然日志量比 STATEMENT 大但可恢复性和数据一致性远好于后者。没有 binlog 又没备份误操作基本只能认栽这也是为什么“备份恢复”永远是数据库基础能力的第一课。4. 事务和锁并发出问题时的底层逻辑4.1 事务的 ACID 与四种隔离级别事务是 InnoDB 的核心特性ACID 四个字母你一定听过原子性Atomicity、一致性Consistency、隔离性Isolation、持久性Durability。简单理解一个事务要么全部成功要么全部失败就像银行转账扣钱和加钱必须同时发生。事务隔离级别决定了事务之间能看到多少对方的数据MySQL 默认是REPEATABLE READ可重复读这点和 Oracle 不同Oracle 默认是 READ COMMITTED。四种隔离级别从松到严隔离级别脏读不可重复读幻读READ UNCOMMITTED可能可能可能READ COMMITTED不可能可能可能REPEATABLE READ默认不可能不可能可能SERIALIZABLE不可能不可能不可能MySQL 的 REPEATABLE READ 通过 MVCC多版本并发控制和间隙锁很大程度上解决了幻读问题所以实际使用中它被广泛接受。但你要知道“可重复读”并不是绝对无幻读在特定条件下比如当前读 范围查询仍可能需要间隙锁来保证。4.2 锁的分类共享锁、排他锁、意向锁、间隙锁“mysql 锁的分类”是基础面试题我按实际场景给你理一遍按粒度分表级锁、行级锁。InnoDB 用行级锁支持更细粒度并发MyISAM 只有表级锁。按模式分共享锁S 锁读锁、排他锁X 锁写锁。多个事务可以同时持有共享锁读同一条记录但排他锁和任何锁都互斥。意向锁InnoDB 在加行锁之前会在表上加意向锁用于表锁和行锁的快速冲突检测。意向共享锁IS、意向排他锁IX它们之间本身不互斥。间隙锁与临键锁在 REPEATABLE READ 下InnoDB 对范围查询会锁住记录之间的“间隙”防止其他事务插入新记录这就是防幻读的关键。临键锁next-key lock是记录锁和间隙锁的组合。说到锁日常开发里最大误区是“SELECT 不是默认加锁的吗”。实际上普通 SELECT 是快照读不加锁这也是 MVCC 高性能的原因。只有SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE、INSERT、UPDATE、DELETE 这些当前读才会真正加锁。4.3 锁表之后的排查思路“mysql 锁表”在热词里排得很靠前说明很多人都遇到过。最常见的锁表原因不是行锁竞争而是长事务 DDL 造成的元数据锁MDL等待。比如有人开了一个事务一直不提交此时 DBA 执行 ALTER TABLEDDL 需要元数据锁结果一直被堵住后续所有对该表的查询、更新全部阻塞。遇到锁表我的排查链路是这样的-- 1. 看当前所有进程 SHOW PROCESSLIST; -- 2. 看 InnoDB 事务信息 SELECT * FROM information_schema.innodb_trx\G -- 3. 找到长时间未提交的事务记下 trx_mysql_thread_id -- 4. 确认后杀掉对应进程 KILL thread_id;如果只是想快速定位“谁堵着谁”还可以查 sys 库的锁等待视图SELECT * FROM sys.innodb_lock_waits\G生产环境里我有一个习惯性动作所有事务方法必须设置合理的超时代码里用 try-finally 确保 commit/rollback 一定执行避免事务意外悬挂。一个十几分钟的悬挂事务就能把整张表卡死这教训我吃得太多次了。5. 进阶工具箱索引、存储过程、常用函数与 SQL 脚本5.1 索引B 树、复合索引与最左前缀索引是 MySQL 性能的核心没有索引的表相当于一本没有目录的书。InnoDB 的索引结构是 B 树叶子节点直接存放行数据聚簇索引二级索引的叶子节点存放的是主键值所以查询时二级索引往往还要回表到主键索引拿完整数据。创建索引的语法CREATE INDEX idx_name_age ON t_user(name, age);索引不是随便建的遵循几个原则区分度高的字段放前面复合索引按“最左前缀”原则生效idx_name_age能匹配WHERE name?和WHERE name? AND age?但匹配不了单独的WHERE age?。另一个原则是索引不是越多越好每多一个索引INSERT、UPDATE 的写放大就越明显。小技巧查询里如果只需要部分字段可以用覆盖索引避免回表。比如SELECT name FROM t_user WHERE age20建idx_age_name后这个查询的所有数据在索引里就能取到Extra 列显示 Using index性能会好很多。5.2 存储过程一个完整的示例“mysql 存储过程”也是热词虽然微服务架构下直接写存储过程不常见但数据迁移、批量操作、报表处理时它依然很有用。基本语法要掌握DELIMITER // CREATE PROCEDURE sp_get_user(IN p_id INT, OUT p_name VARCHAR(50)) BEGIN SELECT name INTO p_name FROM t_user WHERE id p_id; END// DELIMITER ; -- 调用 CALL sp_get_user(1, name); SELECT name;存储过程里常配合游标和循环做批量处理但我要提醒你循环逐行处理的性能很差能一条 SQL 完成的就别用存储过程。存储过程的定位是封装复杂逻辑、减少网络往返而不是替代 SQL 能力。5.3 常用函数字符串、日期与聚合要熟练用的函数分为三类。字符串类CONCAT、SUBSTRING、LENGTH注意中文字符按字节计算时要区分 CHAR_LENGTH、REPLACE、LOCATE。日期类最常用DATE_FORMAT、DATEDIFF、DATE_ADD、NOW。热词里提到的datepart是 SQL Server 的函数MySQL 没有但功能可以用 EXTRACT 或 YEAR()、MONTH()、DAY() 替代-- SQL Server 的 DATEPART(YEAR, date)在 MySQL 写成 SELECT YEAR(create_time), EXTRACT(YEAR FROM create_time) FROM t_user;聚合类COUNT、SUM、AVG、MAX、MIN配合 GROUP BY 和 HAVING 使用。这里有个高频误区WHERE在分组前过滤HAVING在分组后过滤WHERE中不能出现聚合函数。5.4 执行 SQL 脚本的几种姿势“mysql 执行 sql 脚本”在工作交接和部署时很常用。最简单的两种# 命令行直接执行 mysql -uroot -p123456 /path/init.sql # 进入 MySQL 后执行 source /path/init.sql;要注意脚本文件编码和字符集问题如果脚本里包含中文建议文件保存为 UTF-8 无 BOM并在脚本开头加SET NAMES utf8mb4;。另外在命令行直接输入密码会有安全提示生产环境建议用配置文件或环境变量管理密码而不是明文写在 history 里。6. 连接、启动与升级排障实测中遇到的那些坑6.1 ssl 连接错误与认证插件“mysql ssl 连接错误”在热词里高频出现尤其从 8.0 开始默认开启 SSL。客户端用什么方式连接决定了报不报错。命令行客户端连接时可以指定模式mysql -uroot -p123456 -h127.0.0.1 --ssl-modeDISABLED编程语言 JDBC 连接时URL 里直接关掉 SSL 和指定时区String url jdbc:mysql://127.0.0.1:3306/test?useSSLfalseserverTimezoneAsia/Shanghai;另一个更隐蔽的问题是认证插件。MySQL 8.0 默认创建的用户用的是 caching_sha2_password老版本的 JDBC 驱动5.1.x或 PHP mysqli 不支持连接时报Unable to load authentication plugin caching_sha2_password。解决办法要么升级驱动要么把用户改回 mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456;注意 8.0 高版本中 mysql_native_password 默认被禁用需要在 my.cnf 里显式开启后才可用。所以面向老应用最好的方案是升级客户端驱动而不是降低数据库安全等级。6.2 net start mysql 服务无法启动Windows 下net start mysql失败是搜索热词我前面提过排查顺序这里再细讲一遍。核心原则是不要瞎试看日志。MySQL 服务的错误日志默认在 datadir 目录下文件名形如机器名.err。打开看最后几十行通常就有明确原因。常见几类路径错误basedir、datadir 指向不存在目录或路径里的反斜杠没有转义My.ini 推荐用正斜杠。端口被占3306 被之前装过的 MySQL 占了用netstat -ano | findstr :3306查进程要么改端口要么在服务管理器里停掉老服务。data 目录已经初始化过但 my.ini 指向了另一个目录导致找不到系统表。解决办法是保证 datadir 和初始化时一致。权限问题在 Windows 上如果 data 目录权限不足服务启动会失败给当前用户加上完全控制权限。这里特别提醒MySQL 服务一旦装了多个版本注册的服务名和路径要记清楚。mysqld --remove 服务名可以清理清理后再重新注册避免两个服务抢同一个 data 目录。6.3 invalid mysql server upgrade 升级报错热词里有一条[ERROR] [MY-014060] [Server] Invalid MySQL server upgrade:这个报错我见过不少次。它本质上是版本回退或数据目录版本不匹配导致的。MySQL 的系统表有自己的版本号当启动时发现 datadir 里的系统表版本比当前 mysqld 代码版本高时就会拒绝启动因为不支持降级。最常见场景你用 MySQL 8.0.39 初始化了数据目录后来又换了 8.0.20 的二进制启动或者把升级过的高版本 datadir 重新指回旧版本。解决办法很简单不要做版本回退。要么把数据目录用回匹配的版本要么从备份恢复。这种事尤其容易出现在本地环境“装了好几个版本”的人身上我自己的经验是每个数据目录和版本一一对应目录命名直接带上版本号比如 D:/mysql-8.0-data、D:/mysql-5.7-data避免混乱。6.4 周边工具连不上sqoop、flink 同步业务系统里还有不少场景是“外部工具连 MySQL”。热词里的 sqoop 连接不上十有八九是三个原因JDBC 驱动没放对位置、连接串没指定 useSSL、网络权限没开。sqoop 连接串示例sqoop import \ --connect jdbc:mysql://192.168.1.100:3306/test?useSSLfalse \ --username root \ --password 123456 \ --table t_user \ --target-dir /tmp/out驱动 jarmysql-connector-java要放到 $SQOOP_HOME/lib 目录下这是最容易漏的一步。另一个方向是“使用 flink 实现 mysql 同步到 clickhouse”核心思路用 Flink CDC 监听 MySQL binlog把变更事件写入 ClickHouse 的 JDBC sink。这里强调一件事MySQL 必须开启 binlog 且server-id唯一否则 CDC 组件无法拉取日志。这个基础概念能帮你理解为什么这类工具对数据库配置有硬性要求。7. 性能调优的基本姿势从慢查询到参数调整7.1 慢查询日志与 explain性能调优是“mysql 性能调优”热词的核心。我的套路永远是先定位再优化不要上来就改参数。定位工具是慢查询日志和 EXPLAIN。开启慢查询日志slow_query_log1 slow_query_log_file/var/log/mysql-slow.log long_query_time1long_query_time 设成 1 秒超过 1 秒的 SQL 会被记录下来。然后看 EXPLAIN 的执行计划EXPLAIN SELECT u.name, o.amount FROM t_user u JOIN t_order o ON u.id o.user_id WHERE u.status 1;重点关注几个列type 最好是 ref 或 range如果出现 ALL说明是全表扫描key 表示实际用的索引rows 是估计扫描行数Extra 出现 Using filesort 或 Using temporary通常意味着排序或分组没有利用上索引。日常调优 70% 的问题都能通过“建合适的索引”解决真正需要改 SQL 结构的是少数。7.2 常用配置参数参数调整在我的经验里属于“最后手段”但有几个基础参数值得知道。innodb_buffer_pool_size 决定了 InnoDB 缓存池大小理想情况是能装下热数据默认 128M生产通常调到物理内存的 70% 左右。max_connections 默认 151短连接多的应用可以适当调到 200~500但别盲目调太高每个连接都有内存开销。tmp_table_size 和 max_heap_table_size 决定临时表上限大了能减少磁盘临时表。改参数有个原则动态参数用 SET GLOBAL 修改后只对当前实例生效要写进配置文件才永久生效。例如SET GLOBAL max_connections 300;另外调整前必须用监控数据说话。如果连慢日志都没看就直接把缓冲池调大一倍这不是调优是玄学。我自己多年实践下来的一个体会是MySQL 基础知识的真正价值不在于背会多少函数和命令而在于建立一套“出了问题能从底层模型倒推原因”的思维。锁表了先想事务是不是没提交连接失败先想认证插件和网络性能慢先看执行计划和索引。把这几个因果关系真正挂到脑子里比收藏一百个“速查手册”都管用。