简介:一份MySQL中文电子书合集,内容覆盖数据库基础、SQL增删改查、数据类型与存储引擎(InnoDB、MyISAM)、唯一/全文/联合索引创建、EXPLAIN查询分析、存储过程与触发器、视图、数据库范式设计,以及mysqldump备份恢复、用户权限和加密等安全实践,适合从零入门到进阶的开发者系统学习。资源共三十八个HTML文件,按章节和附录拆分,可离线在浏览器中阅读,压缩包体积仅一点三二MB,轻巧便携,目录结构清晰。已有五百一十三人学习下载,既可按顺序通读,也可针对索引调优、备份策略、监控性能指标(CPU、内存、I/O负载)等主题快速检索。书中示例贴近实际运维场景,阅读时动手实践,能帮助读者更扎实地掌握MySQL核心原理、查询优化方法和日常故障排查技能。全书覆盖从基础概念到高级调优的完整知识链,适合自学或作为工作参考手册。
1. 为什么“MySQL 电子书中文”要当工程做,而不是当书读
很多刚入门的读者下载了一堆中文版 MySQL 电子书,被目录里的事务、索引、存储过程、主从复制吓退,最后书签停在第五章。我的建议很直接:把电子书当成一份图纸,把每章知识转成能跑的 SQL 和配置文件,才算真正读完。这篇笔记想跟你聊的,就是“怎么靠一份中文 MySQL 电子书,完成从建库到主从复制的全套落地动作”。适合刚装好 MySQL 却不知道下一步干什么的初学者,也适合用过 MySQL 但总在连接报错、锁等待、配置不生效上翻车的熟手。文章里所有命令我都按常见生产环境习惯来写,你照着敲,踩坑部分我会单独拎出来讲。
2. 从电子书里抽出这份起步路线:选材、装库、建表到第一条查询
2.1 中文资料那么多,先解决“跟哪本走”的问题
我不推荐同时打开三本 MySQL 电子书对比着学,主线一多,最后哪条都走不通。常见做法是选定一本以“实操为主、理论够用”的中文电子书,然后按它的章节目录走一遍:安装与配置、SQL 基础、索引、事务与锁、存储过程与触发器、备份与主从。选书时看三点:第一,有没有针对 MySQL 8.0 的说明,旧书里很多配置项在 8.0 下已经改了默认值;第二,命令是否给完整结果而不是只贴一条孤零零的 SQL;第三,讲锁和事务时是否配了“开两个会话演示”的案例,只看概念不动手,等于没看。
选好主线后,我建议你先做一个动作:建一个专门的实验库,把书里涉及的表结构都敲进去。这一步能同时验证客户端连接、字符集、SQL 模式是否正常,也为后面所有章节的实操准备好场地。我自己习惯给实验库起名study_db,所有折腾都在里面,不碰业务库,翻车了直接 drop 重建,相当于给自己留了一颗后悔药。
2.2 最小可运行的建库建表与查询样例
下面这段脚本覆盖了电子书第一章到第三章常用到的基础操作,我建议你一字不差地敲一遍。
-- 建库,显式指定字符集,避免继承服务器默认设置 CREATE DATABASE IF NOT EXISTS study_db DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE study_db; -- 用户表:id 用自增主键,手机号加唯一索引 CREATE TABLE `user` ( `id` INT NOT NULL AUTO_INCREMENT, `name` VARCHAR(32) NOT NULL COMMENT '用户名', `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_phone` (`phone`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 订单表:关联用户,订单金额用 DECIMAL 而不是 FLOAT CREATE TABLE `orders` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL, `amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00, `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0待支付 1已支付 2已取消', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表'; -- 插入两条测试数据 INSERT INTO `user` (`name`, `phone`) VALUES ('张三', '13800001111'), ('李四', '13800002222'); INSERT INTO `orders` (`user_id`, `amount`, `status`) VALUES (1, 99.90, 1), (2, 19.90, 0); -- 一条带 JOIN 的查询,验证数据没问题 SELECT u.name, o.amount, o.status FROM `user` u JOIN `orders` o ON o.user_id = u.id WHERE o.status = 1;这段脚本里有三个参数值得你注意。字符集我用了utf8mb4,不是utf8,因为 MySQL 的utf8最多存 3 个字节,emoji 和生僻字会报错,这也是很多新手导入数据时乱码的根源。订单金额用DECIMAL(10,2)而不是FLOAT,因为浮点型在比较和累计时会产生精度偏差,电子书里如果没强调这点,你迟早会在对账时发现差几分钱。订单表只建了idx_user_id普通索引,没有建复合索引,因为现在查询条件还很简单,索引不是越多越好。
跑通这段后,你已经完成了“读电子书第一章”的闭环:库、表、数据、查询全部真实存在。接下来就可以往索引和事务这两个硬骨头走了。
3. 把索引和事务两章敲成实验:explain、隔离级别与锁等待
3.1 索引不是“建了就快”,用 explain 验证才是关键
电子书里讲索引时通常会铺开 B+ 树、回表、覆盖索引这些概念。概念要记,但更要落地。我见过太多人在 where 条件列上乱建索引,结果查询没变快,写入反而变慢了。正确路径是先写出查询,再EXPLAIN看执行计划,最后决定要不要建索引。
-- 先用 explain 看这条查询的访问类型 EXPLAIN SELECT * FROM `orders` WHERE `user_id` = 1; -- 建索引后再看一次 ALTER TABLE `orders` ADD KEY `idx_user_status` (`user_id`, `status`); EXPLAIN SELECT * FROM `orders` WHERE `user_id` = 1 AND `status` = 0;看执行计划时重点关注type和key两列。type从好到差一般是system、const、eq_ref、ref、range、index、ALL,如果看到ALL,说明这条查询在做全表扫描,数据量一大必然慢。第一次EXPLAIN时,user_id有单列索引,所以type应该是ref;加了联合索引(user_id, status)后,查询同时过滤两个字段,走的是同一个索引,key会变成idx_user_status,rows估算值也会下降。
有一个电子书不一定写透的细节:联合索引(user_id, status)对“只查user_id”的查询也有效,因为联合索引的最左前缀原则;但对“只查status”的查询无效,因为status不是最左列。所以建联合索引前,你要先想清楚业务里的高频查询到底以哪个字段作为过滤起点。
3.2 事务隔离级别:用两个会话实测脏读、不可重复读与幻读
事务这章最容易看得云里雾里。我的经验是开两个 MySQL 客户端窗口,一个会话写,一个会话读,亲眼看一下不同隔离级别下的差异。先查当前隔离级别:
-- 查看全局和会话级隔离级别 SELECT @@global.transaction_isolation; SELECT @@session.transaction_isolation; -- 把当前会话设为读已提交,注意只影响本会话 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;MySQL 8.0 的默认隔离级别是REPEATABLE READ,很多中文电子书还停留在旧版写法,让你查tx_isolation,这个变量在 8.0 已经改名成transaction_isolation。如果你按旧书敲,会报Unknown system variable 'tx_isolation'。
实操时这样玩:会话 A 开启事务并更新一条数据但不提交,会话 B 查询同一行。在默认的REPEATABLE READ下,B 看到的是旧值;把 B 改成READ COMMITTED后,如果 A 提交了,B 下次查询才能看到新值。这就是“当前读”和“快照读”的直观感受。再进一步,在REPEATABLE READ下,会话 A 插入一条新记录并提交,会话 B 在同一事务里查不到这条记录,但用SELECT ... FOR UPDATE又可能看到,这正是幻读的根源。这块建议你亲手敲一遍,比背十遍定义都管用。
3.3 锁等待的现场排查:谁堵了谁一目了然
锁是生产环境里最容易让 MySQL“假死”的元凶。表象通常是某个更新语句一直卡住不返回,等个几十秒后报Lock wait timeout exceeded。遇到这种问题,不要重启数据库,先查是谁在等锁、谁持着锁:
-- 查看当前所有正在执行的语句和状态 SHOW FULL PROCESSLIST; -- 查看当前事务和锁等待情况 SELECT * FROM performance_schema.data_lock_waits\G -- 杀掉已经阻塞很久的连接,假设线程 id 是 123 KILL 123;SHOW FULL PROCESSLIST里能看到每个连接的Command和State。如果某条UPDATE的State是Waiting for table metadata lock,大概率是有人开着事务没提交,占了元数据锁;如果State是Waiting for lock held by another transaction,那就是典型的行锁竞争。data_lock_waits表能直接看到等锁和被锁的线程关系,比翻日志快得多。
这里有个安全提示:KILL要慎用,先确认线程 ID 对应的连接不是别的同事在跑批量任务。我一般先SHOW FULL PROCESSLIST看Command是不是Sleep且持续时间很长,再决定杀不杀。处理锁问题的第一原则是“找到持有锁的事务并让它尽快提交或回滚”,而不是盲目杀连接。
4. 从安装到连接的常见翻车现场:配置、编码与 SQL 模式排查
4.1 报错 2002 连不上本地 MySQL:socket 路径与 service 状态先查
热词里那个error 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock'几乎每个新手都会遇到。现象很直白:客户端连接时报错,原因是客户端去/tmp/mysql.sock找 socket 文件,但服务端把 socket 文件放在别处,或者服务端根本没起来。先别急着改配置,按顺序做三步排查。
# 第一步:确认服务进程在不在 ps -ef | grep mysqld # 第二步:找到真实的 socket 文件路径 mysql -uroot -p -h 127.0.0.1 -P 3306 # 登录后在 MySQL 里执行: SHOW VARIABLES LIKE 'socket'; # 第三步:如果上面能登录,说明 TCP 方式正常,只是 socket 路径不对 # 连接时显式指定 socket 文件路径,例如: mysql -uroot -p --socket=/var/lib/mysql/mysql.sockps查不到mysqld进程时,问题通常出在服务启动失败,这时去看错误日志比瞎猜效率高。日志默认位置一般在/var/log/mysqld.log或/var/lib/mysql/目录下,具体以你机器上的配置为准。如果ps能看到进程、但 socket 连接不上,多半是客户端和服务端读取的my.cnf不一致,/tmp/mysql.sock是编译时的默认路径,实际路径可能被改到了/var/run/mysqld/下。这个问题有个很笨但有效的解法:连接时不依赖 socket,直接用-h 127.0.0.1走 TCP,绕开路径分歧。
4.2 SQL 模式导致 GROUP BY 报错:only_full_group_by
MySQL 5.7 以后默认开启了ONLY_FULL_GROUP_BY,很多中文电子书里那句“分组查询时可以查非分组字段”已经失效。现象是你执行一条SELECT name, COUNT(*) FROM user GROUP BY status,直接报错,提示Expression #1 of SELECT list is not in GROUP BY clause。原因是 MySQL 要求SELECT列表里的非聚合列必须出现在GROUP BY子句里。
解决有两个方向。业务上如果确实需要查“每组里某个非分组字段的值”,改成用ANY_VALUE(name)或子查询取特定行;如果你确定自己的业务逻辑不需要这个约束,可以关掉,但我不推荐。
-- 查看当前 sql_mode SELECT @@sql_mode; -- 只在当前会话关闭 only_full_group_by SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';关掉ONLY_FULL_GROUP_BY会让一些写法回到 5.6 时代的宽松状态:GROUP BY只保留一列也能查出非聚合列,结果具有随机性,取值不一定来自你期望的那一行。我的习惯是改应用 SQL 而不是改全局模式,因为全局一放开,所有连这个实例的业务都跟着变宽松,万一有人写了依赖随机值的 SQL,排查起来就麻烦了。
4.3 配置文件改了不生效:my.cnf 的作用域和顺序
修改my.cnf后重启 MySQL,发现参数没变,这是另一种高频翻车。原因通常是实例启动时读取的配置文件路径和你改的不是同一个。Linux 下可以用下面的命令确认实际读取顺序:
# 查看 mysqld 实际会读取哪些配置文件 mysqld --verbose --help | grep -A 2 "Default options" # 查看当前实例实际生效的参数值 mysql -uroot -p -e "SHOW VARIABLES LIKE 'max_connections';" # 如果你改的是 [mysqld] 段的参数,确认没写进 [client] 或 [mysql] 段mysqld --verbose --help输出的第一行会列出配置文件读取顺序,常见的是/etc/my.cnf、/etc/mysql/my.cnf、~/.my.cnf,后读的覆盖先读的。你改了/etc/my.cnf,但同目录下还有/etc/mysql/conf.d/里的文件也定义了同名参数,最终生效的是后面加载的那个。排查办法是在配置文件里加一行注释标记,比如# 2025-01-test-change,然后SHOW VARIABLES确认当前值里有没有特征变化。这个办法很土,但复杂环境里特别好用。
4.4 中文乱码:从库到表到连接三个层面对齐
中文乱码问题本质是字符集不一致。服务端character_set_server是latin1,库是utf8mb4,连接又是utf8,三层各说各话,结果就是网页上看到一串问号。我在实验环境里通常这样统一:
-- 查看当前所有字符集相关变量 SHOW VARIABLES LIKE 'character_set_%'; -- 连接层指定字符集,解决当前会话的乱码 SET NAMES utf8mb4;SET NAMES utf8mb4等于同时设置了character_set_client、character_set_connection、character_set_results三个变量。另一个容易忽略的是导入 SQL 文件时的编码,Windows 下用记事本另存为 UTF-8 格式的文件可能带 BOM,MySQL 会把 BOM 当成字段内容的一部分,表现就是第一个字段名或第一行数据前面多了一个不可见字符,导入后查询报Unknown column。解决方案是用sed -i '1s/^\xEF\xBB\xBF//' 文件名.sql去掉 BOM,或者用支持“无 BOM 的 UTF-8”的编辑器重新保存一次。
5. 从单机到主从:binlog、同步配置与远程表同步实操
5.1 先搞懂主从复制在同步什么
中文电子书里主从复制章节通常上来就给配置命令,很少解释原理。我用一句话说明:主库把写入操作记录到 binlog,从库通过 IO 线程拉取 binlog 并写入自己的 relay log,再由 SQL 线程重放 relay log 里的操作到本地数据文件。所以配置核心只有两件事:主库开 binlog,从库配置指向主库的连接信息。
主从复制常见的用途是读写分离、容灾备份和“把远程库的这张表同步到本地”。最后这个需求要特别注意:主从复制是实例级别的,不是表级别的。你没法只同步一张表而不同步其他表,只能通过replicate-do-table参数让从库只重放指定表的变更,但 binlog 仍然会传输所有库的变更。
5.2 主库和从库的最小配置模板
下面这套配置我按 MySQL 8.0 的常见习惯写,主库和从库各自改各自的文件。
# 主库 /etc/my.cnf 追加 [mysqld] server_id = 1 log_bin = /var/log/mysql/mysql-bin binlog_format = row expire_logs_days = 7# 从库 /etc/my.cnf 追加 [mysqld] server_id = 2 relay_log = /var/log/mysql/mysql-relay-bin read_only = 1主库的binlog_format我建议直接用row,不要用statement。row格式记录的是行变更前后的数据,主从数据一致性更好,statement格式记录的是 SQL 原文,遇到NOW()、UUID()这类非确定性函数,从库重放结果可能和主库不一致。expire_logs_days = 7是控制 binlog 保留时长的,避免磁盘被撑爆,但注意从库追不上的时候 binlog 被提前清理会导致复制中断,这个参数要结合从库延迟情况调整。
从库配置里加read_only = 1是为了防止有人误写从库造成数据不一致。注意read_only对拥有 SUPER 权限的账号不生效,所以运维账号别乱发 SUPER 权限。
5.3 从零配置复制的完整命令序列
假设主库 IP 是192.168.1.10,从库 IP 是192.168.1.11,在从库上执行下面的步骤。
-- 第一步:主库上创建复制专用账号,不要用 root 做复制 CREATE USER 'replica'@'192.168.1.%' IDENTIFIED BY 'StrongPass@2024'; GRANT REPLICATION SLAVE ON *.* TO 'replica'@'192.168.1.%'; FLUSH PRIVILEGES; -- 第二步:主库上锁定表并查看当前 binlog 位置 FLUSH TABLES WITH READ LOCK; SHOW MASTER STATUS; -- 记下 File 和 Position 两个值,例如 mysql-bin.000003 和 154拿到 binlog 位置后,如果主库已有业务数据,建议先用mysqldump把数据导出并导入从库,再开启复制;如果从库直接用空库开启复制,会丢失主库历史数据。
-- 第三步:在从库上配置复制源,主库信息按实际填 CHANGE MASTER TO MASTER_HOST='192.168.1.10', MASTER_USER='replica', MASTER_PASSWORD='StrongPass@2024', MASTER_LOG_FILE='mysql-bin.000003', MASTER_LOG_POS=154; -- 第四步:启动复制并检查状态 START SLAVE; SHOW SLAVE STATUS\G第 5 行的\G是把结果纵向输出,列太多时横向看会换行,纵向更清晰。关注Slave_IO_Running和Slave_SQL_Running两个状态,都必须是Yes。Seconds_Behind_Master表示从库落后主库的秒数,理想情况是 0。如果 IO 线程报连接错误,先确认主库账号权限和防火墙;如果 SQL 线程报错,通常是主库已有数据和从库冲突导致的,常见做法是跳过该事务或重新初始化从库数据,这不优雅但有效。
5.4 只同步一张表的写法与边界
只想同步远程库的某张订单表到本地时,很多人的第一反应是弄个定时任务把表导过来。这个方案对数据量小的表没问题,但表到几十 GB 级别,定时全量导入就没法用了。更合理的方案是先用主从复制把整个实例同步过来,再从从库把目标表通过CREATE TABLE ... SELECT或mysqldump导出到本地业务库。从库的replicate-do-table参数可以这样配:
[mysqld] replicate-do-table = sales.orders这个参数写在从库配置里,表示“只复制sales库的orders表”,多张表就写多行。有两个边界你必须知道:第一,replicate-do-table对跨库写入不生效,比如主库执行INSERT INTO sales.orders SELECT * FROM other.temp这种语句,从库可能跳过;第二,主从复制模式下,从库不允许只复制一张表的 DDL 而不同步同库其他表的结构变更,实际操作中如果你只设置了表级过滤,主库执行DROP DATABASE时从库仍然会执行。所以表级复制适合读多写少、表结构稳定的场景,做不到按需订阅某些表却不影响其他表。
6. 读完电子书后的自我验收:性能参数、存储过程与面试题自测
到这一步,你已经把一本中文 MySQL 电子书从“看”变成了“做”。但学没学会,得靠自测。我的验收清单分三块:能不能看懂EXPLAIN输出,能不能写一个带游标的存储过程,能不能说清一道锁相关的面试题。
性能调优这块,我不建议你上来就调innodb_buffer_pool_size。先把SHOW GLOBAL STATUS里的Threads_connected、Slow_queries、Innodb_row_lock_current_waits看一遍,再决定动哪里。常见的快速配置项参考如下,注意这只是起步值,不是生产标准。
| 参数 | 建议起始值 | 说明 |
|---|---|---|
| innodb_buffer_pool_size | 物理内存的 50%~70% | 决定 InnoDB 缓存数据和索引的内存大小 |
| max_connections | 按业务峰值评估 | 默认 151,连接过多时先排查慢查询而不是盲目加高 |
| long_query_time | 2 秒 | 超过该时间的查询记录到慢日志 |
| transaction_isolation | READ COMMITTED 或 REPEATABLE READ | 高并发场景评估是否放宽隔离级别 |
自测存储过程时,我建议写一个带DECLARE CONTINUE HANDLER的循环插入,这是面试里常考的点,也是很多电子书讲得浅的部分。下面这段是我常用的模板:
DELIMITER $$ CREATE PROCEDURE sp_batch_insert(IN cnt INT) BEGIN DECLARE i INT DEFAULT 0; DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; END; START TRANSACTION; WHILE i < cnt DO INSERT INTO `user` (`name`, `phone`) VALUES (CONCAT('用户', i), CONCAT('139', LPAD(i, 8, '0'))); SET i = i + 1; END WHILE; COMMIT; END$$ DELIMITER ; CALL sp_batch_insert(100);DELIMITER $$的作用是把语句结束符临时改成$$,因为过程体里有多条以分号结尾的语句,不换结束符的话客户端会在第一条分号处就认为是完整语句,报语法错误。这个坑在热词“mysql中触发器中分隔符”里也被频繁搜到,触发器和存储过程一样都要处理分隔符。
最后说下我的习惯。每次翻完一本 MySQL 电子书的索引或事务章节,我都会把自己当面试官,拿三道题自测:联合索引最左前缀是什么;RR 隔离级别下幻读为什么还会存在;SELECT ... FOR UPDATE和UPDATE在并发下的行为差异。答不上来就回去翻那本书对应的小节,再不行就开两个会话复现一次。技术文看到这里,希望你能从“读过标题”变成“跑通命令”,这条路我已经替你先踩过一遍了,希望帮到你。
本文还有配套的精品资源,点击获取