news 2026/10/14 10:36:04

MySQL原理到工程落地:安装配置、索引事务、连接池与主从调优

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL原理到工程落地:安装配置、索引事务、连接池与主从调优

简介:MySQL数据库原理及应用(第2版)(微课版)课外拓展PDF以网络玩具销售系统为完整案例,适用于正在学习数据库原理或需要课程设计参考的高校学生。内容依次识别客户、库存玩具信息、订单、玩具品牌、玩具类别、国家、月销售量、运货、购物车、包装等14个实体集及其属性,逐个梳理实体之间的联系并绘制E-R图,随后转换为关系模式,再规范化为第三范式,完整涵盖从概念设计、逻辑设计到关系优化的数据库设计流程。针对实际查询性能需求,资源给出索引与视图的具体SQL示例,包括为Shopper和Orders连接字段创建索引、在Toys和Category的常用属性上创建唯一约束与索引,以及用视图简化购物车总价查询等。多表连接查询部分覆盖购物者姓名、玩具名称、订购数量与价格的联合检索,并附有对应SQL语句,可帮助读者理解JOIN的用法与执行逻辑。资源仅含1个PDF文件,压缩包大小649KB,下载后可直接阅读。已有2363人学习下载。这份PDF适合作为MySQL数据库原理及应用课程的课外拓展资料,也可用于期末复习或小型系统数据库开发参考。

1. 从一份课外拓展 PDF 说起:MySQL 原理与应用该按什么顺序落地

有人把《MySQL数据库原理及应用(第2版)(微课版)-课外拓展.pdf》当复习提纲翻,能背出 B+ 树、ACID、四种隔离级别,真要动手建一套环境时却卡在第一步:初始化报错、客户端连不上、字符集乱码。原理和应用之间缺的不是知识点,是一条能照着敲、每步都能验证的路径。这份资料里讲过的概念,配上微课视频看一遍确实顺,但看完之后往哪走,很多人是断档的。

这篇把那些「讲了但没给顺序」的部分重排一遍:先把 MySQL 跑起来,用最小实验把索引和事务验证一遍,再落到增删改查、存储过程、连接池这些真实项目里的写法,然后做性能调优和主从复制,中间单独留一章讲我踩过的坑。适合刚做完数据库课程设计、准备接真实项目的人,也适合工作几年但没系统验证过原理的开发者。

2. 把 MySQL 跑起来:版本选择、离线安装与初始化后必改的五个参数

2.1 版本和发行版怎么定,别在下载页上耗半天

新起项目直接上 MySQL 8.0,这是目前最省事的选择。5.7 在存量系统里还大量存在,但官方已经不再提供补丁更新,新项目用它等于一开始就背了个包袱。两者真正的差别不在功能列表,而在几个默认值上:8.0 默认认证插件换成了caching_sha2_password,老版本的客户端和 JDBC 驱动连上去会直接报认证失败;默认字符集换成了 utf8mb4,反而是很多老教程里手写的三字节utf8成了坑。

更值得留意的是发行版。在 Linux 上用系统包管理器装mysql-server,装出来的很可能是 MariaDB。它跟 MySQL 同源,但已经不是一回事,权限表结构、部分函数、复制协议细节都有差异,照着 MySQL 的教程敲下去,遇到的现象会对不上号。判断方法很简单,登录后执行SELECT VERSION();,字符串里带 MariaDB 的就说明你装的不是 MySQL。

版本当前定位适合场景必须注意
MySQL 8.0官方主线版本新项目、新环境默认 caching_sha2_password,老驱动要升级
MySQL 5.7存量维护为主已有老系统不再有安全补丁,不要用于新项目
MariaDB独立分支系统仓库自带与 MySQL 存在语法和权限差异

提示:内网离线环境部署时,不要在容器里跑一个默认配置的 MySQL 就往里灌生产数据,容器默认的innodb_buffer_pool_size通常只有一百多 MB,磁盘层还多一层开销。

2.2 Linux 离线安装 MySQL 的完整命令链

内网服务器连不上外网是常态,这里用官方 glibc 版 tar 包走一遍完整流程,比 RPM 包更通用,也方便以后同机放多个版本做升级演练。

# 1. 建独立的 mysql 用户和组,不要让数据库进程以 root 身份运行 groupadd mysql useradd -r -g mysql -s /bin/false mysql # 2. 解压到 /usr/local 并做软链接,以后换版本只改链接不动配置 tar -xf mysql-8.0.xx-linux-glibc2.17-x86_64.tar.xz -C /usr/local/ ln -s /usr/local/mysql-8.0.xx-linux-glibc2.17-x86_64 /usr/local/mysql # 3. 初始化数据目录,datadir 单独放一块盘,避免日志写满撑爆根分区 mkdir -p /data/mysql && chown -R mysql:mysql /data/mysql /usr/local/mysql/bin/mysqld --initialize --user=mysql \ --basedir=/usr/local/mysql \ --datadir=/data/mysql # 4. 注册成系统服务,开机自启 cp /usr/local/mysql/support-files/mysql.server /etc/init.d/mysqld systemctl daemon-reload && systemctl enable mysqld && systemctl start mysqld # 5. 初始密码是随机生成的,只在错误日志里出现一次,先捞出来再改 grep 'temporary password' /data/mysql/*.err

第 1 步不做会留下真实风险:一旦应用侧被注入,数据库进程的运行身份直接决定了攻击者能读写到哪些文件。第 3 步的--initialize和--initialize-insecure是两种模式,前者生成随机密码并写进错误日志,后者建一个空密码的 root,只适合本地一次性实验,别在服务器上用。第 5 步的临时密码如果不及时改,日志文件被谁翻到谁就能进库。

2.3 初始化之后必须改的五个参数

装完不改配置,后面会遇到一堆看起来莫名其妙的问题:时间差八小时、emoji 存进去变问号、批量导入被严格模式拦下来。下面这几个值建议在第一次启动后就写进my.cnf的[mysqld]段。

[mysqld] # 字符集用 utf8mb4,三字节的 utf8 存不了 emoji 和部分生僻字 character-set-server = utf8mb4 collation-server = utf8mb4_0900_ai_ci # 时区必须显式指定,否则容器和 JDBC 之间会差 8 小时 default-time-zone = '+08:00' # 连接数上限,要和后面的连接池上限一起算,不是越大越好 max_connections = 500 # 严格模式:数据截断、除零这类操作直接报错而不是静默写入脏数据 sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION # 表名统一小写,跨平台迁移时少一类问题 lower_case_table_names = 1

改完配置重启用临时密码登录,把密码和账号体系一起收拾干净:

-- 改掉临时密码,注意 8.0 默认的密码强度策略 ALTER USER 'root'@'localhost' IDENTIFIED BY 'StrongPass!2024'; -- 业务不要用 root 连,按网段建一个只给必要权限的账号 CREATE USER 'app'@'10.0.%' IDENTIFIED BY 'AppPass!2024'; GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'app'@'10.0.%'; FLUSH PRIVILEGES; -- 改完立刻验证一遍关键变量 SHOW VARIABLES LIKE 'character_set%'; SELECT @@lower_case_table_names, @@max_connections, @@time_zone, @@sql_mode;

lower_case_table_names是这几个参数里唯一的后悔药没有的那种:它决定了表名在磁盘上的存储方式,必须在初始化时定好,8.0 里在数据目录已经建好之后再改这个值,实例大概率直接起不来。max_connections也不是越大越好,每个连接都要占线程栈和会话内存,500 已经能覆盖大多数单机场景,真要更高并发应该走连接池加读写分离,而不是把这个数字一路往上堆。

3. 让原理站住脚:用 EXPLAIN、双会话事务和锁等待表做三个验证实验

3.1 索引不是玄学:一条 EXPLAIN 就能看出 B+ 树用没用上

先把实验环境造出来。这张订单表的结构在真实项目里很常见:自增主键、一个业务唯一键、一个用来做条件查询的联合索引。

CREATE TABLE t_order ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL, order_no VARCHAR(32) NOT NULL, amount DECIMAL(10,2) NOT NULL DEFAULT 0.00, status TINYINT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_status (user_id, status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 用存储过程灌 10 万行,逐条 INSERT 会慢到让你怀疑人生 DELIMITER $$ CREATE PROCEDURE sp_seed(IN n INT) BEGIN DECLARE i INT DEFAULT 0; WHILE i < n DO INSERT INTO t_order(user_id, order_no, amount, status) VALUES (FLOOR(1 + RAND()*1000), CONCAT('NO', LPAD(i, 10, '0')), ROUND(RAND()*500, 2), FLOOR(RAND()*4)); SET i = i + 1; END WHILE; END$$ DELIMITER ; CALL sp_seed(100000);

数据有了,现在对比三条查询的执行计划:

-- 命中联合索引的最左列,type=ref,key 是 idx_user_status EXPLAIN SELECT id, order_no FROM t_order WHERE user_id = 37 AND status = 1; -- 跳过 user_id 直接查 status,联合索引失效,type=ALL 全表扫 EXPLAIN SELECT id, order_no FROM t_order WHERE status = 1; -- 只查索引里有的列,Extra 会出现 Using index,即覆盖索引,不回表 EXPLAIN SELECT user_id, status FROM t_order WHERE user_id = 37;

看执行计划重点盯四个字段。type从好到坏大致是const > ref > range > index > ALL,出现 ALL 基本就是要优化了。key是实际选中的索引,如果是 NULL 说明一个都没用上。rows是优化器估算的扫描行数,估算和实际差太远时可以用ANALYZE TABLE重新采样统计信息。Extra里最有价值的是Using index(覆盖索引,不回表)和Using filesort(需要额外排序,深分页时是重灾区)。联合索引的最左前缀原则不是背出来的,是这两条 EXPLAIN 对比出来的。

3.2 事务隔离级别:开两个会话把脏读和不可重复读复现一遍

原理课讲隔离级别,最容易滑过去的就是「同一个查询在同一个事务里返回了不同的值」。开两个终端连同一个库,手工走一遍就有体感了。

-- 会话 A:先把隔离级别降到 READ COMMITTED SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; BEGIN; SELECT balance FROM account WHERE id = 1; -- 读到 100 -- 会话 B:另一个终端,改数据并提交 BEGIN; UPDATE account SET balance = 200 WHERE id = 1; COMMIT; -- 回到会话 A:同一个事务里再查一次 SELECT balance FROM account WHERE id = 1; -- READ COMMITTED 下读到 200 COMMIT;

把会话 A 的隔离级别换成REPEATABLE READ再走一遍,第二条查询会稳定返回 100。差异的来源是 MVCC:RR 级别下事务第一次读的时候生成了一个快照(read view),之后的普通 SELECT 都读这个快照对应的 undo 版本,所以值不变。但要注意,快照读和当前读是两回事:

-- 当前读:加锁读,会看到别的事务已提交的最新值,不受快照限制 BEGIN; SELECT balance FROM account WHERE id = 1 FOR UPDATE; -- 读到 200 COMMIT;

UPDATE、DELETE、SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE都是当前读。很多「隔离级别是不是失效了」的困惑,本质是把快照读和当前读混为一谈。

3.3 锁等待和死锁:从 performance_schema 里把元凶捞出来

死锁不用背定义,两个会话就能造出来:

-- 会话 A BEGIN; UPDATE account SET balance = balance - 10 WHERE id = 1; -- 会话 B BEGIN; UPDATE account SET balance = balance - 10 WHERE id = 2; -- 会话 A:想去拿 id=2 的锁,等 B 放 UPDATE account SET balance = balance + 10 WHERE id = 2; -- 会话 B:反向去拿 id=1 的锁,形成环,InnoDB 检测到后回滚其中一方 UPDATE account SET balance = balance + 10 WHERE id = 1;

线上真出现锁等待时,靠猜是没用的,直接查这两张表:

-- 当前所有行锁,LOCK_DATA 告诉你锁在哪一行 SELECT ENGINE_LOCK_ID, OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_DATA FROM performance_schema.data_locks; -- 谁在等谁,把阻塞源和被阻塞者对上号 SELECT * FROM performance_schema.data_lock_waits\G -- 最近一次死锁的完整现场,含有两条冲突 SQL 和各自的持锁情况 SHOW ENGINE INNODB STATUS\G

data_locks里的LOCK_MODE值得记一下:X是排他锁,S是共享锁,后面带GAP的是间隙锁,RR 级别下范围条件的插入阻塞多半因它而起。处理思路就三条:把业务里访问多张表的顺序统一;事务里不要夹带外部接口调用这种耗时操作;能拆成小事务就别写一个大事务从头包到尾。

4. 落到写代码:增删改查、存储过程与连接池的工程写法

4.1 批量写入、UPDATE 语法和翻页排序的实际写法

增删改查这四个字在教材里是入门内容,在项目里第一批出问题的往往就是它们。先说导入,Excel 另存成 CSV 再走LOAD DATA,比程序里一条条 INSERT 快一到两个数量级。

-- 导入 CSV,IGNORE 1 LINES 跳过表头 LOAD DATA LOCAL INFILE '/tmp/order.csv' INTO TABLE t_order FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 LINES (user_id, order_no, amount, status); -- 多表关联更新:批量推进某个等级用户的订单状态 UPDATE t_order o JOIN t_user u ON u.id = o.user_id SET o.status = 2, o.created_at = NOW() WHERE u.level = 3 AND o.status = 1;

LOAD DATA的两个开关要注意:LOCAL是从客户端读文件,服务端需要开local_infile;去掉LOCAL则是从服务端所在机器读,权限和路径都不一样。关联 UPDATE 时如果两张表数据量都很大,先用SELECT把要改的主键查出来再分批WHERE id IN (...),比一条大 SQL 直接怼上去稳得多。

排序分页是另一个高频翻车点:

-- 反例:深翻页越翻越慢,扫描行数随偏移量线性增长 SELECT * FROM t_order ORDER BY id LIMIT 1000000, 20; -- 正例:游标法,记上一页最后一条的 id,每次只扫 20 行 SELECT * FROM t_order WHERE id > 1000000 ORDER BY id LIMIT 20;

深翻页慢的原因不是LIMIT,是ORDER BY加偏移量要求数据库先定位并丢弃前 N 行。游标法要求排序字段唯一且有序,自增主键天然满足;如果用created_at这类可能重复的列做游标,得加上主键做二次排序保证稳定。

4.2 存储过程什么时候值得写,什么时候是负债

存储过程的争议一直很大,我的判断标准很实际:逻辑稳定、被多个应用共用、网络往返成本高的批处理,值得写;业务规则天天变、需要频繁改和调试的,别写。下面这个是典型的对账场景。

DELIMITER $$ CREATE PROCEDURE sp_settle_order( IN p_user_id BIGINT, OUT p_settled INT ) BEGIN -- 异常处理必须在声明变量之后、可执行语句之前 DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; -- 出错整体回滚,不留半截数据 SET p_settled = -1; -- 用约定的负值告诉调用方失败了 END; START TRANSACTION; UPDATE t_order SET status = 3 WHERE user_id = p_user_id AND status = 2; SET p_settled = ROW_COUNT(); -- 受影响行数,不是查询行数 INSERT INTO t_settle_log(user_id, cnt, created_at) VALUES (p_user_id, p_settled, NOW()); COMMIT; END$$ DELIMITER ;

调用和取返回值:

CALL sp_settle_order(37, @n); SELECT @n; -- 返回结算的订单数,-1 表示执行失败

几个关键点:DELIMITER不是 SQL 语法,是客户端用来区分语句结尾的标记,很多人第一次写存储过程报语法错就是漏了它。DECLARE ... HANDLER的位置有严格顺序,写在可执行语句后面会直接报错。ROW_COUNT()返回的是上一条 DML 影响的行数,跟FOUND_ROWS()不是一回事。

存储过程真正的负债在于版本管理:它躺在数据库里,不在代码仓库里,改了哪一版对不上号,只能靠SHOW CREATE PROCEDURE一条条导出来对比。团队如果没有把存储过程纳入发布流程,慎用。

4.3 连接池参数怎么算,JDBC 的 useSSL 到底要不要开

应用侧最常见的问题不是 SQL 写错,是连接池配错。下面是一份 HikariCP 的典型配置。

// JDBC URL 里的参数每一个都有实际含义,别整段复制 String url = "jdbc:mysql://10.0.1.20:3306/shop" + "?useUnicode=true&characterEncoding=utf8mb4" // 与库端字符集对齐 + "&useSSL=false&allowPublicKeyRetrieval=true" // 关闭加密握手,见下文说明 + "&serverTimezone=Asia/Shanghai" // 必须与库端时区一致 + "&rewriteBatchedStatements=true"; // 批量插入真正合并成多值 SQL HikariConfig cfg = new HikariConfig(); cfg.setJdbcUrl(url); cfg.setUsername("app"); cfg.setPassword("AppPass!2024"); cfg.setMaximumPoolSize(20); // 单实例建议 = CPU 核数 * 2 + 磁盘数,过量反而拖慢 cfg.setMinimumIdle(5); // 维持少量长连接,避免突发流量现开连接 cfg.setConnectionTimeout(3000); // 拿不到连接 3 秒就失败,别让请求无限等 cfg.setIdleTimeout(600000); // 空闲 10 分钟回收 cfg.setMaxLifetime(1740000); // 比 MySQL 的 wait_timeout 小 2~3 分钟

rewriteBatchedStatements=true是这里面性价比最高的一个参数,不开它,JDBC 的addBatch还是会把 SQL 一条条发给服务端;开了之后驱动会把它们拼成一条多值 INSERT,批量写入的差距能到几倍。maxLifetime必须小于服务端的wait_timeout,否则连接会在池子里被服务端先掐掉,应用拿到的就是一个已经失效的连接,报错信息通常还很含糊。

useSSL=false只是表示这条连接不走加密握手,能省一次协商开销,不代表连接本身更安全——真要加密,正确做法是导入服务端证书、开启校验,而不是靠一个布尔值切换。至于连接池上限怎么定,不是越大越好:数据库的连接数上限、应用实例数、每个实例的池大小相乘才是总连接数,二十个应用实例每个配 100,就是两千连接打过去,MySQL 会先崩给你看。

5. 避坑与排查:连接、字符集、事务这三类坑我踩过一遍

5.1 连接类报错:2002、1045、1130 分别说明什么

现象:本地mysql -uroot -p直接报ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2),但服务明明在跑。原因:客户端连本机时默认走 Unix socket 而不是 TCP,客户端编译时约定的 socket 路径和实例实际监听路径不一致,在多版本共存或者自定义socket参数的环境里非常常见。解决:先ps -ef | grep mysqld看实例实际启动参数里的--socket,然后客户端显式指定mysql -uroot -p -S /data/mysql/mysql.sock,或者在my.cnf的[client]段把socket写成同一个路径。想验证 TCP 通不通就用-h 127.0.0.1 -P 3306,这条走的是网络栈,能把 socket 问题和端口问题区分开。

现象:ERROR 1045 (28000): Access denied for user 'app'@'10.0.1.5',密码确认没打错。原因:两种可能。一是 8.0 默认的caching_sha2_password认证插件,老版本的客户端或驱动不支持;二是账号的 host 限定是localhost,而你是从远程 IP 连的,MySQL 会认为是两个不同的账号。解决:确认插件用SELECT user, host, plugin FROM mysql.user WHERE user='app';。驱动太老就升驱动,没有升级条件再考虑改插件,改的时候记得FLUSH PRIVILEGES。host 不对就补一个对应网段的授权,不要图省事写成'app'@'%'。

现象:ERROR 1130 (HY000): Host '10.0.2.7' is not allowed to connect to this MySQL server。原因:这个报错发生在认证之前,本质是没有任何一条mysql.user记录的 host 段匹配你的来源 IP。解决:按最小网段建授权,CREATE USER 'app'@'10.0.2.%',而不是直接开全局。生产库把%开出去,等于把暴力破解的门槛降到了只剩密码强度。

5.2 字符集和大小写:两个初始化之后改不动的东西

现象:昵称里的 emoji 存进去变成????,或者两个库之间导数据出现乱码。原因:老配置里写的utf8在 MySQL 里是三字节实现,最多表示基本多文种平面,emoji 和部分生僻字都要四字节,落到utf8列上只能被替换掉。解决:库、表、列、连接四层全部统一成utf8mb4。连接层由 JDBC URL 里的characterEncoding=utf8mb4控制,漏了这一层,前面三层改对了照样乱码。已经建好的表用ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4逐张转,转之前备份,大表记得用在线 DDL 工具。

现象:在 Windows 上开发时表名能建,迁到 Linux 之后查同一张表报Table doesn't exist。原因:lower_case_table_names在不同操作系统上的默认值不一样,Windows 和 macOS 默认不区分大小写,Linux 默认区分。解决:这个参数必须在初始化时就定,8.0 里数据目录建好之后再改,实例通常直接起不来,也没有官方的回退路径。统一按lower_case_table_names=1初始化,SQL 里的表名全用小写,这是唯一能跨平台稳定运行的做法。

5.3 事务和复制:看起来没问题但数据不对

现象:一个跑了十几分钟的事务提交后,磁盘空间没释放,ibdata和 undo 表空间一路涨。原因:长事务期间产生的 undo 版本不能被清理,MVCC 快照还挂着,purge 线程只能往后排队。解决:先查SELECT * FROM information_schema.innodb_trx ORDER BY trx_started;把跑得最久的事务找出来,再回到业务代码里找有没有「开事务之后调外部接口」这种写法。事务边界要贴着数据库操作,把网络调用、文件读写全部提到事务外面。

现象:RR 隔离级别下,两个事务往同一范围插入数据,其中一个一直等着。原因:RR 下范围条件加锁会带间隙锁,锁的不是某一行,是行与行之间的空隙,插入落到空隙里就会被阻塞。解决:确认业务是否真的需要 RR 级别,很多团队的默认做法是把隔离级别降到 RC,间隙锁随之失效。如果必须留在 RR,就把范围条件的边界收窄,避免无条件的大范围UPDATE。

现象:主从复制搭好之后,偶尔从库读到的还是旧数据,应用侧表现为「明明下单成功了,查订单列表却没有」。原因:复制是异步的,主库提交完成不代表从库已经应用完这条 binlog。解决:先看Seconds_Behind_Source,但这个值只是估算,网络抖动时会失真。真正可靠的判断是 GTID 集合是否一致。业务上强制写后读一致的做法,是把刚写完那段时间的读请求也路由到主库,或者用WAIT_FOR_EXECUTED_GTID_SET做一次显式等待,而不是靠调小复制延迟来蒙。

6. 从单机到主从:用 GTID 复制和 sys schema 验证你的调优真的生效

6.1 用 GTID 搭主从,并用三个字段确认它真的同步了

主从复制是单机 MySQL 走向可用的第一步,也是「数据库同步工具」这类工具在底层做的事情。手工搭一遍能把这些工具的黑匣子打开。

-- 主库:建一个专用于复制的账号,权限只给 REPLICATION SLAVE CREATE USER 'repl'@'10.0.1.%' IDENTIFIED BY 'ReplPass!2024'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'10.0.1.%'; -- 确认 GTID 已开启,ON 表示用 GTID 定位而不是手工算 binlog 位点 SELECT @@gtid_mode, @@enforce_gtid_consistency;
# 主库做一份一致性备份,--single-transaction 对 InnoDB 不锁表 # --source-data=2 会把当时的位点以注释形式写进备份文件,方便核对 mysqldump -uroot -p --single-transaction --source-data=2 \ --set-gtid-purged=ON --databases shop > /backup/shop.sql
-- 从库:把备份导入之后,指向主库并启动复制 CHANGE REPLICATION SOURCE TO SOURCE_HOST='10.0.1.20', SOURCE_PORT=3306, SOURCE_USER='repl', SOURCE_PASSWORD='ReplPass!2024', SOURCE_AUTO_POSITION=1; START REPLICA; SHOW REPLICA STATUS\G

看SHOW REPLICA STATUS只盯三个字段就够:Replica_IO_Running和Replica_SQL_Running都必须是Yes,任何一个为No都说明复制已经断了,Last_Error里会有具体原因;Retrieved_Gtid_Set和Executed_Gtid_Set要一致,两个集合的差值就是从库还没应用的事务。Seconds_Behind_Source可以参考但不要当唯一依据,主库空闲时它的值也会跟着归零,掩盖真实延迟。

6.2 打开慢查询和 sys schema,用数字确认调优不是自我感觉

看不到指标就没法判断优化有没有用。慢查询日志是成本最低的一手数据:

-- 运行期临时打开,正式环境写进 my.cnf 持久化 SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 0.5; -- 超过 500ms 记一条 SET GLOBAL log_queries_not_using_indexes = ON; -- 没走索引的也记,便于发现遗漏

更省事的是直接用sys库,它把 performance_schema 的原始数据加工成了可读视图:

-- 按总耗时排序,直接告诉你哪张表最该优化 SELECT * FROM sys.schema_table_statistics ORDER BY total_latency DESC LIMIT 10; -- 分析出具体哪条 SQL 拖后腿(格式化后的语句) SELECT * FROM sys.statement_analysis ORDER BY total_latency DESC LIMIT 10;

调优前后要拿同一组指标对照,别只看单一数字:

指标采集方式关注点
慢查询条数SHOW GLOBAL STATUS LIKE 'Slow_queries'优化前后做差,绝对值下降才有意义
缓冲池命中率Innodb_buffer_pool_read_requests与Innodb_buffer_pool_reads的比值低于 99% 说明大量读走了磁盘
排序落盘比例Sort_merge_passes的增长速度持续增长意味着内存排序区不够
临时表落盘Created_tmp_disk_tables与Created_tmp_tables比例高通常是排序或分组字段缺索引
连接数水位Threads_connected与max_connections持续贴近上限,该调整的是架构不是参数

我自己的教训是把innodb_buffer_pool_size设成物理内存的 50% 到 70% 之下,先留足系统缓存;改完之后不看指标,凭「感觉快了」就收工,结果一周后发现慢查询只是被挤到了别的时段。现在这套流程固定成了习惯:改任何参数之前先记一遍基线,改完等一个业务高峰,再拉一次sys.statement_analysis对照。没有前后对比的调优,本质上和没做一样。

需要补充的是,这套验证流程并不复杂,难点在于愿不愿意每次都做。我现在接手一套新库,第一件事不是看表结构,而是先确认三件事:lower_case_table_names是几、innodb_buffer_pool_size有多大、主从的 GTID 集合是不是一致的——这三件事决定了后面所有的排查会不会走弯路。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 10:35:53

PDF批量去水印:矢量/光栅/伪水印识别与安全清除实践

简介&#xff1a;这是一款面向办公人员、文档处理者及PDF批量管理需求用户的轻量级去水印工具&#xff0c;专为高效清除PDF中文字、图片或图形类水印而设计&#xff0c;解决因公司标识、隐私信息等水印导致的文档展示不专业、分发受限等问题。资源包共17个文件&#xff0c;含核…

作者头像 李华
网站建设 2026/10/14 10:35:18

MeshNet 深度解读:在三角网格上直接做卷积的工程实践

1. 为什么 MeshNet 值得单独拿出来讲第一次看到 MeshNet 这个工作的时候&#xff0c;我的反应是"终于有人把这件事讲清楚了"。它处理的是一个在三维视觉和几何深度学习圈子里被反复讨论、但一直缺少统一答案的问题&#xff1a;怎么让神经网络直接吃三角网格这种非欧几…

作者头像 李华
网站建设 2026/10/14 10:34:59

TTL方案详解:彻底解决ThreadLocal在线程池中的上下文传递难题

我先说结论&#xff1a;如果你还在用原生ThreadLocal做线程池场景下的上下文传递&#xff0c;那你迟早要吃大亏。不是吓唬你&#xff0c;线上环境里ThreadLocal给我挖过的坑&#xff0c;排队能排出三米远&#xff1a;业务代码一上线程池&#xff0c;traceId 满天飞&#xff0c;…

作者头像 李华
网站建设 2026/10/14 10:34:23

Webstorm打开uniapp uvue文件报红处理方案

1. Webstorm打开uniapp uvue文件报红处理方案1.1. 安装 WebStorm 的 uni-app x 插件 CtrlAltS 打开设置 → Plugins&#xff08;插件市场&#xff09;搜索&#xff1a;Uniappx Language Service&#xff08;作者 Lansyn&#xff0c;插件 ID:30438&#xff09;安装 → 重启 WebS…

作者头像 李华
网站建设 2026/10/14 10:34:19

水下航行器外流场仿真:子午线压力系数分布全解析

做水下航行器外流场仿真的人&#xff0c;一定都体会过这种焦虑&#xff1a;几何建好了、网格画了几百万、求解器跑了上千步&#xff0c;盯残差曲线盯到眼花&#xff0c;最后真正能让方案优劣“一锤定音”的&#xff0c;往往是表面那条压力系数分布曲线。前阵子我完整做了一遍某…

作者头像 李华
网站建设 2026/10/14 10:34:14

SpringBoot医疗检验结果自动比对系统设计与实践

1. 项目概述 在医疗信息化领域&#xff0c;检验结果的准确性直接关系到临床诊断和治疗方案的制定。传统的人工比对方式不仅效率低下&#xff0c;而且容易因疲劳导致差错。这个基于SpringBoot的病人检验结果自动比对系统&#xff08;项目代号n48s1a6n&#xff09;正是为解决这一…

作者头像 李华