ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MySQL实战:从安装建表到排错调优,掌握索引与事务

MySQL实战:从安装建表到排错调优,掌握索引与事务 简介一份面向MySQL学习者、开发者和运维人员的中文电子书合集以HTML格式按章节分页编排覆盖SQL语法基础、数据类型与存储引擎、索引优化与EXPLAIN分析、触发器/存储过程/视图、范式化设计、备份恢复与安全权限管理等核心主题兼顾新手入门与日常实战查阅需求。压缩包内共38个HTML文件整体约1.32MB除第127章之外还收入附录AK其中包含常用命令、参数设置等参考信息在浏览器中打开即可按目录连续阅读也可针对性地检索特定知识模块。已有513人学习使用资源结构清晰、体量轻量适合用来系统构建MySQL知识体系并为数据库设计、性能调优以及故障恢复提供可随时查用的离线手册。1. 从MYSQL电子书到能跑通的环境先回答“这份资源值不值得下”我在本地环境折腾 MySQL 的时间不算短最烦的不是语法记不住而是每次报错都散落在搜索引擎的各个角落查一条换一个页面上下文全断了。这份 MYSQL 电子书是中文的主线很清楚装环境、建表写 SQL、索引事务、存储过程、主从复制、排错。它不追求把官方文档抄一遍而是把一条路走通再告诉你路的边界在哪。你如果是刚接触 MySQL按它的章节顺序把环境跑起来基本不会卡在半路如果你已经在用 MySQL只是被某类报错反复折磨这本书的排错章节能直接当查表用。我读完之后最大的感受是它不是用来“读”的是用来“照着做”的。下面我把整本书的实践路径拆开每一步配上命令和参数顺便把我在复现过程中踩过的坑都标出来。2. 架构与安装存储引擎选型、Linux离线与Docker落地路径2.1 架构分层与存储引擎InnoDB 凭什么当默认要复现一本 MySQL 电子书里的所有实验第一步不是装软件而是搞清楚 MySQL 为什么这样设计。它的整体架构可以分成四层连接层负责认证和线程处理服务层做语法解析、优化和缓存引擎层负责存储和事务存储层才是真正落盘的文件系统。日常写 SQL 只跟服务层打交道但性能问题几乎都出在引擎层和存储层。存储引擎是 MySQL 区别于其他数据库的一大特点InnoDB 和 MyISAM 是最常见的两个选项。电子书里用了不少篇幅讲引擎我把它浓缩成一张对比表对比项InnoDBMyISAM事务支持 ACID 事务commit/rollback 有效不支持事务锁粒度行级锁支持并发写表级锁写锁阻塞所有读写崩溃恢复redo log undo log自动恢复依赖修复工具易丢数据外键支持不支持全文索引5.6 之后支持原生支持适用场景绝大多数业务系统只读报表、日志表如果你的 SQL 里出现过 update 一条记录却把整张表锁住的情况多半是用了 MyISAM。现在的安装包默认引擎就是 InnoDB电子书的实验也都是基于 InnoDB 写的所以别为了“轻量”去改默认引擎。除非你明确知道自己在做日志分析这类只读场景否则 InnoDB 基本不会选错。安装之前还有一个容易被忽略的点MySQL 8.0 的认证插件默认是 caching_sha2_password很多旧版客户端连不上。要是你用的 Navicat 版本偏老连接时报认证插件错误要么换新版客户端要么在创建用户时指定 mysql_native_password。这个坑在电子书里没细说但实操时发生率很高。2.2 Linux 离线安装RPM 包顺序与初始化命令离线环境装 MySQL 是电子书里比较硬核的一节。生产网段常常没有外网yum 源不可用只能拿 RPM 包手动装。常见做法是先在一台能上网的机器上下载对应版本的 RPM 包再拷贝进去。安装顺序有讲究依赖关系是 core 在 libs 之后client 和 server 最后# 检查是否已有残留的 mysql 或 mariadb rpm -qa | grep -E mysql|mariadb # 按依赖顺序安装缺一个都会报冲突或依赖缺失 rpm -ivh mysql-community-common-*.rpm rpm -ivh mysql-community-libs-*.rpm rpm -ivh mysql-community-client-*.rpm rpm -ivh mysql-community-server-*.rpm # 初始化数据目录注意 5.7 以上用 --initialize mysqld --initialize --usermysql # 启动服务 systemctl start mysqld systemctl status mysqld这里两个细节要说明。第一--initialize会在数据目录生成一个临时 root 密码写在错误日志里也就是/var/log/mysqld.log中temporary password那一行。用这个密码登录后必须马上改密码否则任何 SQL 都执行不了。第二RPM 安装默认数据目录是/var/lib/mysql如果之前初始化过需要先清空再重新初始化否则报错“Data directory not initialized”。改密码的常见做法是# 用临时密码登录 mysql -uroot -p # 强制修改密码8.0 对密码策略有要求 ALTER USER rootlocalhost IDENTIFIED BY YourStrongPass_2024; FLUSH PRIVILEGES;参数说明IDENTIFIED BY后面接的密码要满足 validate_password 组件的策略默认要求长度至少 8 位且包含大小写字母、数字和特殊符号。如果你只是想本地实验可以把策略调低但不建议在生产环境这么做。2.3 Docker 安装一条更适合复现的环境路径如果只是为了跟着电子书做实验Docker 是效率最高的方式它的好处是随时可以销毁重建不会把宿主机搞乱。我的做法是一个容器跑一个版本需要对比 5.7 和 8.0 行为差异时互不影响docker run -d \ --name mysql-demo \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123 \ -v /opt/mysql-data:/var/lib/mysql \ -v /opt/mysql-init:/docker-entrypoint-initdb.d \ mysql:8.0这条命令里几个参数值得说清楚。-p 3306:3306是把容器的 3306 端口映射到宿主机如果你宿主机已经装了 MySQL改成-p 3307:3306避免冲突。MYSQL_ROOT_PASSWORD是容器第一次启动时设置 root 密码的环境变量只有数据目录为空时生效。/opt/mysql-init目录下放.sql脚本容器首次启动时会按文件名顺序自动执行这很适合初始化测试库和测试账号。Docker 方式真正要留意的是数据持久化。很多人跑容器不做-v挂载容器删掉以后数据全没这是 Docker 装 MySQL 最常见的翻车点。挂载以后/var/lib/mysql里的数据文件直接落在宿主机上容器删了重新 run数据还在。万一初始化出问题清掉/opt/mysql-data下的文件重新跑一次即可成本比卸载 RPM 低一个量级。2.4 安装后必做的三件事字符集、时区、账号权限环境跑起来以后电子书的很多实验会涉及中文数据字符集不对会出现乱码或者“Incorrect string value”报错。我的习惯是初始化之后立刻确认三件事-- 查看当前字符集与排序规则 SHOW VARIABLES LIKE character_set_server; -- 全局改成 utf8mb4 SET GLOBAL character_set_server utf8mb4; SET GLOBAL collation_server utf8mb4_unicode_ci; -- 查看时区 SHOW VARIABLES LIKE time_zone;字符集必须用utf8mb4不是utf8。utf8在 MySQL 里其实是 utf8mb3最多存 3 个字节而 emoji 和一些生僻汉字要 4 个字节插进去就报错。排序规则一般选utf8mb4_unicode_ci兼容性比_general_ci好。时区问题在 JDBC 连接时尤其明显电子书后面有一节专门的连接池配置都是从这里引出来的。常见的serverTimezoneAsia/Shanghai只是客户端设置服务端最好也统一否则跑NOW()函数时得到的时间和你的笔记本对不上。3. 建库建表与常用 SQLUPDATE、排序、类型转换中的细节3.1 建库建表字段类型选错是后面所有问题的源头电子书里所有实验都建立在表结构上建表看似简单选错类型才是后续性能问题的根源。整数类型要看取值范围TINYINT是 1 字节INT是 4 字节BIGINT是 8 字节主键能用INT就不要用BIGINT每行多 4 个字节千万行就是 40MB 的索引差距。金额字段用DECIMAL绝对不要用FLOAT或DOUBLE二进制浮点数在精度上会出问题。CREATE TABLE orders ( id INT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 订单号, 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), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段建表语句里有几个值得说透的点。AUTO_INCREMENT的自增主键要求必须是索引列通常是主键。DECIMAL(10,2)表示总长度 10 位、小数 2 位最大能存 99999999.99一般订单金额够用。DEFAULT CURRENT_TIMESTAMP是 5.6.5 之后才支持的写法旧的写法是在created_at上手动填NOW()效果一样但多了应用层的工作量。TINYINT存状态码比用VARCHAR存“pending”“paid”更省空间查询也更快代价是可读性差一些需要靠注释补。3.2 UPDATE 语法单表更新与多表更新的边界搜索热词里mysql update语法排得很靠前说明这是大多数人实际写的时候会卡住的地方。最基础的UPDATE是单表更新写法很直白UPDATE orders SET status 1 WHERE order_no 20240601001;这行 SQL 背后有一个重要的参数影响行数。MySQL 默认只要更新的值和原值一样影响行数就是 0这会影响你在 JDBC 里判断是否更新成功。如果想让受影响行数体现真实匹配数连接串要加useAffectedRowstrue默认是false。多表更新是另一个高频翻车点。比如要根据用户的会员等级批量修改订单折扣常见做法是 UPDATE 一个表然后从另一个表取值UPDATE orders o JOIN users u ON o.user_id u.id SET o.discount CASE WHEN u.level VIP THEN 0.9 ELSE 1.0 END WHERE o.status 0;这里要强调一个边界多表 UPDATE 里的WHERE决定的是哪些行进入更新集合而不是只更新orders表。如果你不加WHERE所有匹配的行都会被波及影响行数可能远超预期。我见过有人写多表更新忘掉WHERE把整张表的折扣都改掉了最后只能靠备份恢复。所以在执行这类带 JOIN 的 UPDATE 之前先把 SELECT 写出来看结果集确认无误再改成 UPDATE。3.3 排序与表达式int5 这类计算的执行顺序mysql排序这个话题看起来简单但 ORDER BY 和索引的关系很微妙。ORDER BY不一定能用到索引比如对orders.created_at排序如果WHERE条件里已经定位到user_id而索引是idx_user_id排序字段不在索引里MySQL 就要做 filesort也就是把结果集先读出来再在内存或磁盘上排序。数据量大的时候这个操作可能比查询本身还慢。再看mysql中int5这个搜索词说的是表达式计算。在 SELECT 里写id 5很安全MySQL 会逐行计算SELECT id, id 5 AS shifted_id FROM orders WHERE status 0;但如果把表达式放到 WHERE 条件里索引就失效了-- 这个写法索引失效因为每行的 id 都要先算完再比较 SELECT * FROM orders WHERE id 5 100; -- 改写成这样索引就能用 SELECT * FROM orders WHERE id 95;这个细节很值得记住索引列参与了运算优化器就没法用 B 树的顺序查找只能全表扫。电子书里对这类表达式没有展开讲但实际面试和工作中都容易在这里翻车。3.4 字符串转日期STR_TO_DATE 与 DATE_FORMAT 配对使用搜索热词里有mysql将字符串转为日期这是导入外部数据时的刚需。MySQL 不会自动把任意字符串当日期要用STR_TO_DATE显式转换-- 把 2024/06/01 10:30:00 转成 DATETIME SELECT STR_TO_DATE(2024/06/01 10:30:00, %Y/%m/%d %H:%i:%s); -- 把 2024-06-01 转成 DATE SELECT STR_TO_DATE(2024-06-01, %Y-%m-%d);格式化符有几个容易写错的%H是 24 小时制%h是 12 小时制%i是分钟%m是月份。如果你把%i写成%M得到的是英文月份缩写或者 NULL不报错但结果错。反向操作是DATE_FORMAT用于把日期字段格式化成字符串导出SELECT DATE_FORMAT(created_at, %Y%m%d) FROM orders;我一般会在导入 CSV 之前先跑一条 SELECT 验证格式能正确转换看到 NULL 就说明格式符不匹配而不是数据本身有问题。这类问题排查成本很低但没经验的人很容易在格式符上反复试错。4. 索引、事务锁与存储过程进阶部分怎么读才不白读4.1 索引创建索引的时机与 EXPLAIN 验证电子书的进阶章节从索引讲起理由是它直接影响 90% 的慢查询。mysql创建索引这个搜索词的背后大多是想解决“查询越来越慢”的问题。创建索引的语法很固定ALTER TABLE orders ADD INDEX idx_status_created (status, created_at);这里我想展开讲的是联合索引。idx_status_created这个索引status在左、created_at在右查询条件里必须包含最左侧的status索引才能生效。只查created_at用不到这个索引这就是所谓的最左前缀原则。如果你的业务经常单独按created_at查就得额外建单列索引。索引建得对不对要用 EXPLAIN 验证别靠猜EXPLAIN SELECT * FROM orders WHERE status 1 AND created_at 2024-06-01; -- 输出里的 key 显示用了哪个索引 -- type 从 system const eq_ref ref range index ALL 依次变差key这一列显示的是优化器实际选的索引名rows是预估扫描行数。如果 type 是ALL说明全表扫描索引没生效。常见原因是 WHERE 条件里对索引列做了函数运算或者联合索引的列顺序不对。记住这个判断链条你就能自己排查索引问题而不是盲目的加索引。4.2 事务与锁从锁表现象反推隔离级别mysql锁表这个搜索词背后通常是一个具体的痛苦场景某个 UPDATE 执行之后其他会话的 SELECT 也卡住了。这大概率不是 SELECT 本身的问题而是事务没有提交行锁一直没释放。先看现象再反推原因-- 会话A开启事务更新一行但不提交 BEGIN; UPDATE orders SET status 1 WHERE id 1; -- 会话B查询同一行会一直阻塞 SELECT * FROM orders WHERE id 1;会话 B 卡住是因为 InnoDB 默认的隔离级别是 REPEATABLE READ会话 A 的更新拿到了这行的排他锁没提交之前锁不释放会话 B 的当前读要等锁。解决办法是从两个层面入手一是检查应用代码里是否忘了COMMIT或ROLLBACK事务开而不关是最常见的锁源二是用SHOW PROCESSLIST看正在执行的语句SHOW FULL PROCESSLIST; -- 如果发现长时间 Sleep 的会话可以把它 kill 掉 KILL 12345;SHOW FULL PROCESSLIST这个命令能从全局视角看到哪些会话在运行、哪些在 Sleep。很多线上锁表事故都是代码里开了事务中间查了接口最后没提交连接池又一直复用这个连接导致锁越积越多。电子书里在锁这一节反复强调事务必须短平快我完全认同。4.3 存储过程与触发器DELIMITER 是第一道坎存储过程是 MySQL 进阶绕不开的内容搜索热词里mysql存储过程、mysql声明存储过程、mysql中触发器中分隔符三个词指向同一个痛点定义过程体内的分号会被 MySQL 客户端当成语句边界截断。解决办法是先用DELIMITER改变分隔符DELIMITER // CREATE PROCEDURE sp_update_order( IN p_order_id INT, IN p_new_status INT, OUT p_affected INT ) BEGIN UPDATE orders SET status p_new_status WHERE id p_order_id; SET p_affected ROW_COUNT(); END // DELIMITER ;参数说明IN是输入参数OUT是输出参数ROW_COUNT()返回上一条语句影响的行数。在 JDBC 里调用CallableStatement.registerOutParameter才能读到OUT参数的值。DELIMITER改完以后一定要改回来很多人在 SQL 文件里忘了恢复默认分隔符导致后续语句全部报错。触发器跟存储过程类似也受DELIMITER影响而且还要注意NEW和OLD关键字DELIMITER // CREATE TRIGGER trg_orders_before_update BEFORE UPDATE ON orders FOR EACH ROW BEGIN IF NEW.status 1 AND OLD.status 0 THEN INSERT INTO order_log(order_id, action, created_at) VALUES (OLD.id, PAID, NOW()); END IF; END // DELIMITER ;触发器的NEW表示更新后的行OLD表示更新前的行只能在BEFORE UPDATE或AFTER UPDATE的上下文中使用。一个很隐蔽的坑是在触发器里执行 INSERT 到另一张表如果那张表也有触发器会形成级联触发调订单数据时可能会让写入路径变得不可控。电子书对触发器的态度是“能用但别依赖”我很认同现在新项目里用触发器的越来越少了逻辑尽量放在应用层。5. 避坑socket 连接失败、启动报错、SSL 告警与主从复制中断5.1 error 2002socket 路径对不上现象执行mysql -uroot -p报错ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock但systemctl status mysqld显示服务明明是 running。原因客户端默认找/tmp/mysql.sock而服务端实际把 socket 文件放在别处常见路径如/var/lib/mysql/mysql.sock或/run/mysqld/mysqld.sock。排查方式是看配置文件里 socket 参数的实际值grep -r socket /etc/my.cnf /etc/my.cnf.d/解决显式指定 socket 路径连接或者加软链。我一般推荐显式指定软链在系统重启后经常失效mysql -uroot -p --socket/var/lib/mysql/mysql.sock如果你用的是 Navicat 或 JDBC直接连接 127.0.0.1:3306 走 TCP不受 socket 路径影响。5.2 mysqld.service 启动失败从日志反查现象systemctl start mysqld之后systemctl status mysqld显示 activerunning但过几秒就死了或者干脆启动失败日志里提示Unit mysqld.service failed。原因数据目录权限不对或者上次异常退出留下了损坏文件。RPM 安装时数据目录所有者应该是 mysql 用户如果你用 root 手动初始化过数据目录的属主会变成 rootmysqld 以 mysql 用户启动时无法读写就退出。解决先看日志再动手日志位置一般是/var/log/mysqld.logtail -50 /var/log/mysqld.log如果是权限问题直接改属主chown -R mysql:mysql /var/lib/mysql如果是ibdata1损坏导致启动崩溃常规手段是先用innodb_force_recovery拉起来备份数据等级从 1 到 6 逐级尝试这个参数在/etc/my.cnf的[mysqld]段临时加。我一般会先加innodb_force_recovery1试试能读到多少数据备份完成立刻把参数去掉恢复正常模式。5.3 MySQL SSL 连接错误参数对齐现象用 JDBC 连接报错SSL connection error或者告警Establishing SSL connection without servers identity verification is not recommended。原因MySQL 8.0 默认开启 SSL而客户端连接串没有明确指定 SSL 行为。这个报错的本质是客户端和服务端 SSL 参数没有对齐。解决连接串上显式加参数jdbc:mysql://127.0.0.1:3306/db?useSSLtrueverifyServerCertificatefalseserverTimezoneAsia/ShanghaiuseSSLtrue表示启用 SSL 加密传输verifyServerCertificatefalse表示不校验服务端证书这样既能加密通信又不需要自己签证书。如果你在内网环境且对加密没有硬性要求也可以用useSSLfalse直接关掉但要注意 MySQL 8.0 某些驱动版本在useSSLfalse时会直接报错需要升级驱动或让sslModeDISABLED。这个参数的优先顺序是sslMode高于老版本的useSSL新驱动建议直接用sslModeDISABLED。5.4 主从复制中断从库状态与 POS 排查现象主库正常写入从库数据不更新SHOW SLAVE STATUS里Slave_IO_Running是 Yes 但Slave_SQL_Running是 No或者两个都是 No。原因最常见的情况是从库执行某个中继日志里的 SQL 失败比如主库执行了 DROP TABLE从库这张表已经不存在或者从库上有写入冲突。另一种是网络原因导致 IO 线程断掉。解决先看状态再定策略不要盲目的STOP SLAVE; START SLAVE;SHOW SLAVE STATUS\G重点关注三列Last_SQL_Error、Seconds_Behind_Master、Relay_Log_File。看到Last_SQL_Error里明确的语句后有两种常见处理方式。如果错误是重复执行导致的幂等冲突可以跳过这一条STOP SLAVE; SET GLOBAL sql_slave_skip_counter 1; START SLAVE;如果错误较多从库数据已经落后太多最稳的方式是重新同步在主库做全量备份恢复到从库再重新配置 CHANGE MASTER TO 的二进制日志位置。做之前务必记录当前MASTER_LOG_FILE和MASTER_LOG_POS这两个值对不上恢复后主从依然对不齐。主从复制是电子书最后一章的核心内容它的排错逻辑跟前面完全不同前面的 SQL 错了会立刻报错主从复制错了会悄悄延迟最好用的监控指标就是Seconds_Behind_Master和两个 Running 状态位。我现在的习惯是每天早上看一眼这两个值比任何复杂的运维框架都直接。6. 验证与调优用 EXPLAIN、mysqlslap 和连接池参数把书里的结论变成自己的电子书最后部分讲了性能调优这一部分不能只读要当成实验来做。我先说验证的手段任何调优结论都要先量化再动手。慢查询日志和 EXPLAIN 是量化的基础EXPLAIN 的type、key、rows三个字段分别对应访问方式、实际使用的索引、预估扫描行数。rows从一万降到一百就是索引生效的直接证据。压测工具方面MySQL 自带的 mysqlslap 够用不需要一上来就上复杂工具# 模拟 100 个并发客户端每个执行 500 次查询 mysqlslap --concurrency100 --iterations1 \ --querySELECT * FROM orders WHERE status1 AND created_at 2024-01-01 \ --create-schematest --number-of-queries500 \ -uroot -p--concurrency控制并发线程数--number-of-queries控制总请求数跑出来的结果会显示平均耗时。注意 mysqlslap 的并发只是单机多线程模拟不是分布式压测但用来做索引前后的对比足够。连接池参数是电子书里另一个容易被忽略的点。我见过很多项目死在连接池配置上最大连接数设成 200但 MySQL 的max_connections只有默认的 151瞬间把数据库打满。我的建议是连接池的最大连接数设为max_connections的 60% 左右留出给运维操作的空间。JDBC 连接串里的connectionTimeout和socketTimeout也要明确定前者是拿连接的等待时间后者是 SQL 执行的最大时长后者不设的话慢查询会把线程池拖垮。我这几年折腾 MySQL 得来的最大教训是任何书里的结论都要在自己的环境里跑一遍验证。以前我照着网上教程配主从复制配完以为成功了半个月后才发现从库一直中断只是因为启动时没看SHOW SLAVE STATUS。从那以后我每次部署完 MySQL都强制走一遍检查流程字符集、时区、SSL 参数、主从两个 Running 状态、连接池最大连接数。这套流程跑完这份 MYSQL 电子书里的大部分内容才真正变成你自己的经验遇到问题也敢拍胸脯说见过。希望帮到你。本文还有配套的精品资源点击获取
返回列表