运维常见题-MySQL维护
DeepSeek V4 Flash 辅助生成。为确保内容的可靠性,笔者已对大部分关键论点进行了人工复核与校验。但鉴于大模型的固有局限,本文仍可能存在认知偏差或未尽准确之处。若您在阅读中发现存疑或矛盾之处,欢迎反馈讨论,笔者将及时核实与修正。🤔 MySQL 有哪些备份方案?
MySQL备份方案按备份内容分为逻辑备份(导出 SQL/文本)与物理备份(复制数据文件),按备份时状态分为冷/温/热备份,按范围分为全量、增量、差异备份。实践中没有单一"最佳方案",而是组合使用:定期全量 + 增量 + binlog 归档,才能实现时间点恢复(PITR)。选型核心看两个指标:RTO(恢复要多快)和 RPO(最多丢多少数据)。第一层:逻辑备份(导出为 SQL/文本)
mysqldump:官方自带、最经典的单线程逻辑备份工具,输出可读 SQL,适合中小数据量、跨版本迁移、按需导出单表。InnoDB在线热备组合:mysqldump --single-transaction --source-data=2 --all-databases > backup.sql。--single-transaction把隔离级别设为REPEATABLE READ并发起START TRANSACTION,对InnoDB做一致性快照且不阻塞读写;--source-data=2把binlog位点以注释形式写入dump文件(MySQL 8.0.26起官方推荐--source-data,MySQL 8.0中旧名--master-data仍可用)。- 恢复:
mysql < backup.sql。 - 注意:
--single-transaction只保证InnoDB一致,MyISAM/MEMORY表可能处于不一致状态;且与--lock-tables互斥。
mysqlpump:MySQL 5.7引入的并行逻辑备份工具,曾用于加速导出。但官方文档明确mysqlpump自MySQL 8.0.34起弃用(deprecated),预计未来版本移除,不推荐新项目使用,官方建议改用mysqldump或MySQL Shell。MySQL Shell dump utilities(官方新一代推荐) :util.dumpInstance()/util.dumpSchemas()/util.dumpTables()导出,util.loadDump()导入。- 特性:多线程并行、按表分块、压缩、进度显示;输出到本地目录,或写入
OCI Object Storage/S3兼容服务 / · 存储。 - 默认
consistent=true使用LOCK INSTANCE FOR BACKUP保证一致性(MySQL Shell 8.0.29之前要求BACKUP_ADMIN权限;8.0.29起无该权限时降级为额外一致性检查)。 - 数据一致性仅对
InnoDB表保证。 - 示例:
mysqlsh --uri root@localhost -- util dump-instance /backup/dump --threads=4;恢复mysqlsh --uri root@localhost -- util load-dump /backup/dump。
- 特性:多线程并行、按表分块、压缩、进度显示;输出到本地目录,或写入
SELECT ... INTO OUTFILE/ 导出CSV:按需导出查询结果,常用于数据交换,不是完整备份方案。
第二层:物理备份(复制数据文件)
冷备份(停机复制) :停服后直接复制整个
datadir(ibdata1、*.ibd、redo log等),恢复最快最简单,代价是停机窗口。Percona XtraBackup:开源的InnoDB热备物理备份工具(事实标准),复制数据文件同时跟踪redo log,支持全量/增量/压缩/加密/流式(xbstream)。- 全量:
xtrabackup --backup --target-dir=/backup/full; - 增量:
xtrabackup --backup --target-dir=/backup/inc1 --incremental-basedir=/backup/full。 - 恢复前必须
xtrabackup --prepare --target-dir=/backup/full(用redo前滚、回滚未提交事务,把备份变成一致状态),再xtrabackup --copy-back --target-dir=/backup/full。 - 版本严格对应
MySQL大版本:MySQL 8.0用XtraBackup 8.0.x,MySQL 8.4用8.4.x,MySQL 9.x用对应9.x系列,跨大版本不兼容(详见扩展信息)。
- 全量:
MySQL Enterprise Backup(mysqlbackup):Oracle MySQL商业版附带的物理备份工具,官方文档在大型物理备份场景推荐。文件系统/存储快照:
LVM snapshot、ZFS snapshot或云厂商快照(如EBS),秒级生成、恢复快。- 流程:先
FLUSH TABLES WITH READ LOCK保证一致性 →lvcreate -L 10G -s -n mysql-snap /dev/vg0/mysql→ 挂载快照复制数据 → 卸载并删除快照。前提是datadir位于支持快照的卷上。
- 流程:先
第三层:
binlog日志备份(PITR的关键)- 只有全量/增量备份只能恢复到备份时刻。要回放到任意时间点,必须持续归档
binary log:用mysqlbinlog --read-from-remote-server从远端拉取,或直接复制binlog文件异地保存。 - 恢复时先还原最近备份,再重放备份位点之后的
binlog:mysqlbinlog binlog.0000xx binlog.0000yy | mysql。GTID环境下用GTID集合定位位点更可靠。 - 典型场景:凌晨 3 点误删数据,备份是 0 点,重放 0 点到 2:59 的 binlog 即可精确恢复。
- 只有全量/增量备份只能恢复到备份时刻。要回放到任意时间点,必须持续归档
第四层:云数据库托管备份
AWS RDS/ 阿里云RDS/ 腾讯云等托管数据库自带自动备份(每日全量 +binlog/redo持续备份)与一键时间点恢复(PITR),支持手动快照。- 最省心,但受平台保留期与功能限制;跨云/下云迁移仍需自行导出(
mysqldump或MySQL Shell dump)。
第五层:方案选型与策略
- 数据量小、迁移/换库 →
mysqldump或MySQL Shell dump;数据量大、要求快速恢复(短RTO)→XtraBackup物理备份;云上托管实例 → 平台自动备份 +PITR。 - 遵循
3-2-1原则(3份拷贝、2种介质、1份异地),并定期演练恢复——备份不可恢复等于没有备份。 - 主从复制 /
Group Replication不是备份:复制是实时重放binlog,人为误删、逻辑错误会同步传播到所有节点;官方做法是复制到副本后再对副本执行备份,且备份必须独立保存、验证可恢复。
- 数据量小、迁移/换库 →
协助记忆
- 逻辑备份像把整本书重新誊写成文本——可读、可改、速度慢;物理备份像直接打包硬盘——快而完整,但只能在同"环境"下还原。
- 口诀:全量打底、增量跟进、binlog 回放、异地 3-2-1。
进阶思考
主从复制能当备份用吗?
- 不能。复制只是实时同步的冗余和高可用,误删/坏数据会经
binlog同步传播到所有副本;官方建议"先复制到副本,再在副本上做备份",并且备份本身要独立保存和定期验证。
- 不能。复制只是实时同步的冗余和高可用,误删/坏数据会经
逻辑备份和物理备份怎么选?
- 看数据量与恢复目标:几十 GB 以内、要可读/可编辑、迁移换库选逻辑(
mysqldump/MySQL Shell);大库、要求分钟级恢复选物理(XtraBackup);云上托管实例直接用平台备份。物理备份恢复快但依赖MySQL版本,逻辑备份可移植但大库恢复很慢。
- 看数据量与恢复目标:几十 GB 以内、要可读/可编辑、迁移换库选逻辑(
为什么必须有
binlog备份才能做PITR?- 全量/增量快照只到备份时刻;
binlog记录此后每一次变更,重放binlog才能把数据回放到"备份之后、灾难之前"的任意时间点,这是RPO趋近于零的基础。
- 全量/增量快照只到备份时刻;
XtraBackup恢复时为什么必须先--prepare?- 热备时复制出的数据文件与
redo log不在同一时点、内部不一致;--prepare用redo前滚已提交事务、回滚未提交事务,把备份"抹平"成一致可用状态,之后才能--copy-back放回datadir。
- 热备时复制出的数据文件与
扩展信息
XtraBackup与MySQL版本对应矩阵:XtraBackup 8.0.x↔MySQL 8.0(该系列已结束支持 EOL,官方建议升级到 8.4);XtraBackup 8.4.x↔MySQL 8.4(不支持8.0与9.x服务器);XtraBackup 9.x↔ 对应MySQL 9.x系列。跨大版本互不兼容,选错工具版本备份会直接失败。mysqldump权限要点:--single-transaction在gtid_mode=ON且gtid_purged=ON|AUTO时需要RELOAD或FLUSH_TABLES权限;默认--opt开启(含--quick,逐行读取避免大表占用内存)。
🤔 InnoDB 与 MyISAM 存储引擎有什么区别?
InnoDB是事务型、行级锁、崩溃可恢复的通用默认引擎;MyISAM是非事务型、表级锁、面向读多写少场景的轻量引擎。MySQL 5.5.5起InnoDB成为默认引擎,MySQL 8.0起系统表与数据字典全部基于InnoDB;MyISAM仍受支持但需显式ENGINE=MYISAM指定,且已不再演进(8.0 起移除其分区支持)。第一层:数据安全与一致性(本质差异)
- 事务(ACID) :
InnoDB支持事务,具备提交、回滚、MVCC多版本控制;MyISAM不支持事务,一条语句要么成要么败,多条语句之间无法原子化 - 崩溃恢复:
InnoDB靠redo log前滚 +undo log回滚 +doublewrite buffer防半页写,崩溃后自动恢复;MyISAM无redo/undo、无事务性恢复,异常宕机表易损坏,需myisamchk/mysqlcheck手工修复(仅可配置启动时自动检查) - 外键:
InnoDB原生支持外键约束,保证引用完整性;MyISAM不支持,只能靠应用层保证 - 锁粒度:
InnoDB行级锁(辅以意向锁、间隙锁、临键锁),高并发下写互斥面小;MyISAM表级锁,写操作锁整张表,读多写少尚可,读写混合时互相阻塞(例外:表尾INSERT可与SELECT并发)
- 事务(ACID) :
第二层:存储与索引结构
- 文件形态:
InnoDB数据与索引存于表空间(独立表空间为.ibd文件);MyISAM数据文件.MYD+ 索引文件.MYI分离;表定义在8.0起统一由数据字典管理(取代.frm) - 索引组织:两者索引都是
B+Tree,但组织方式不同 ———InnoDB主键是聚簇索引,数据行直接存在主键叶子节点,二级索引叶子存主键值,回表取数据;MyISAM是非聚簇,索引与数据分离,索引叶子存行指针,数据行物理存储与索引无关 - 行数统计:
MyISAM直接保存表行数,无WHERE的COUNT(*)直接返回(O(1));InnoDB不维护精确行数,COUNT(*)需扫描(优化器会尽量选最小的二级索引)
- 文件形态:
第三层:功能特性差异
- 全文与空间索引:
MyISAM原生支持FULLTEXT、SPATIAL;InnoDB自5.6起支持FULLTEXT、5.7起支持SPATIAL——— 中文全文检索需配合ngram parser,且InnoDB全文检索只能看到已提交数据 - 压缩:
MyISAM可用myisampack压缩为只读表,省空间;InnoDB支持表压缩(KEY_BLOCK_SIZE)与页压缩 - 自增列:
MyISAM支持复合索引中非首列作为自增列(多列联合自增);InnoDB自增列必须是索引首列;8.0起InnoDB将自增计数器持久化到redo log与数据字典,重启/回滚不再回退计数 - 缓存:
MyISAM只有key cache(索引缓存),数据依赖操作系统文件缓存;InnoDB的buffer pool同时缓存数据页与索引页,命中率高、受控性好 - 创建方式:
1 2CREATE TABLE t1 (id INT PRIMARY KEY, name VARCHAR(20)) ENGINE=InnoDB; -- 默认引擎,可省略 CREATE TABLE t2 (id INT PRIMARY KEY, name VARCHAR(20)) ENGINE=MyISAM; -- 显式指定
- 全文与空间索引:
第四层:性能与选型场景
MyISAM适合:读多写少、无事务要求、可接受手工修复风险的场景(如历史归档表、只读报表、经myisampack压缩的冷数据);顺序读与无 WHERE 计数有优势InnoDB适合:高并发读写、事务、外键、需要崩溃安全的业务表——这也是它成为默认引擎的原因- 选型红线:生产环境默认
InnoDB;除非明确只读归档场景,否则不要为了"读快"选MyISAM——— 表级锁在并发写时反而更慢,且无崩溃恢复的数据损失代价远大于那点读性能
协助记忆
InnoDB像银行账本 ——— 每笔流水都有日志(redo/undo)、坏了能对账恢复、按账户(行)记账互不干扰;MyISAM像纸质登记簿——记一笔要整本锁住、没有备份日志、被水淹(宕机)后只能手工重抄,但查"总共有多少页"翻一眼封面就知道- 口诀:行锁事务能恢复,生产选
InnoDB;读多写少可归档,才用MyISAM
进阶思考
MyISAM读真的比InnoDB快吗?- 仅在特定场景成立 ——— 无
WHERE的COUNT(*)直接返回行数、顺序全表扫描;高并发下表级锁的读写互斥会抵消读优势。且"快"是以数据安全为代价换的,业务表不值得
- 仅在特定场景成立 ——— 无
MyISAM表级锁是不是读写完全互斥?- 不是。它支持并发插入(
Concurrent Inserts):表尾无空洞的INSERT可与SELECT并发执行,这是它在读多写少场景能扛住一定写入的原因
- 不是。它支持并发插入(
为什么网上说"
MySQL 8.0弃用了MyISAM"?- 官方文档并未把
MyISAM引擎标记为deprecated;事实是8.0起InnoDB全面接管系统表与数据字典、MyISAM不再提供分区支持、功能停止演进,等于"不再被选择"而非"被宣布弃用"——两者表述要区分
- 官方文档并未把
InnoDB做中文全文检索要注意什么?- 默认解析器按空格分词,不适合中文;需启用
ngram parser(5.7.6+),按字符n-gram切分,并注意全文索引只检索已提交数据
- 默认解析器按空格分词,不适合中文;需启用
扩展信息
- 边缘化时间线:
5.5.5起InnoDB成为默认引擎 →8.0起系统表/数据字典全部基于InnoDB、MyISAM分区支持被移除 →MyISAM仅作为兼容选项保留 8.0自增改进:自增计数器随redo log持久化并在检查点写入数据字典,解决旧版"重启后计数器从MAX(列)重新推断、可能回退"的问题
- 边缘化时间线:
🤔 binlog 日志有什么作用?
binlog(二进制日志)是MySQL Server层的逻辑日志,记录所有数据变更操作,官方定义的两大核心用途是主从复制与时间点恢复(PITR),审计是它的衍生用途。8.0起二进制日志默认开启、默认采用ROW记录格式;它和InnoDB的redo log通过两阶段提交配合,既保证崩溃一致性,又支撑数据恢复到任意时刻。第一层:
binlog是什么- 记录什么:记录所有可能改变数据的语句(
DML、DDL);不记录SELECT/SHOW等不修改数据的查询。STATEMENT格式下"可能造成变更"的语句(如匹配0行的DELETE)也会被记录,ROW格式下不记录 - 日志层级:
binlog属于MySQL Server层,与存储引擎无关,任何引擎都可用;而redo log是InnoDB引擎层的日志 - 默认开启:
8.0起二进制日志默认开启(5.7默认关闭,需手动log_bin开启);8.0.14起支持加密(binlog_encryption)
- 记录什么:记录所有可能改变数据的语句(
第二层:三大核心作用
- 主从复制:主库将
binlog作为变更数据源发送给从库,从库写入relay log后重放,实现数据同步——这是官方列出的第一用途 - 时间点恢复(PITR) :先恢复全量备份,再用
mysqlbinlog重放增量binlog,把数据库恢复到指定时间点或binlog位置——误删数据、误操作后的救命手段,官方列出的第二用途 - 变更审计:
binlog记录了完整的变更历史(谁改了什么、何时改的),可作为审计依据——这是事实上的衍生用途,官方文档未将其列为主要目的
- 主从复制:主库将
第三层:记录格式
STATEMENT:记录SQL语句本身,日志量小;但非确定性函数(NOW()、UUID()、RAND()等)在主从执行结果可能不一致ROW:记录行级变更的前后映像,最安全准确、主从绝对一致,但日志量大;8.0起为默认格式,且自8.0.34起binlog_format参数被弃用,未来只保留ROWMIXED:默认按STATEMENT记录,遇到非安全语句自动切换为ROW,兼顾日志量与安全性DDL恒为statement记录:无论binlog_format是什么,DDL语句始终以statement(Query事件)格式写入binlog———8.0的变化是DDL变为原子操作(Atomic DDL),而非改用row事件- 行映像控制:
binlog_row_image决定ROW格式记录哪些列 ———FULL(前后映像全列)、MINIMAL(仅变更列 + 主键,日志最小)、NOBLOB(省略未变更的BLOB/TEXT列) - 事务压缩:
8.0.20起支持binlog_transaction_compression,对ROW格式的binlog事务做压缩以省空间(默认关闭,按需开启)
第四层:与
redo log的区别及一致性保证redo log(InnoDB物理日志) :记录数据页的物理修改,循环覆盖、大小固定,专用于崩溃恢复(前滚 + 回滚)binlog(Server层逻辑日志) :记录语句或行变更,追加写入、可长期保留,服务于复制与PITR- 两阶段提交:事务提交时先写
redo log(prepare状态)→ 写binlog→ 再提交redo(commit);崩溃恢复时,已成功写入binlog的prepared事务予以提交,未写入的予以回滚,并把binlog截断到最后有效位置 ——— 这就是"binlog与InnoDB数据不丢不一致"的机制 - 刷盘保证:
sync_binlog=1(默认值)每次提交将binlog刷盘,配合innodb_flush_log_at_trx_commit=1,保证已提交事务不丢;sync_binlog=N(N>1)或0时,最多丢失最近未刷盘的提交组
第五层:关键参数与运维
- 开启与格式:
log_bin(开启)、binlog_format(格式)、binlog_row_image(行映像) - 保留与清理:
binlog_expire_logs_seconds控制保留时长(默认30天),自动清理发生在启动与FLUSH LOGS时;旧参数expire_logs_days已弃用并在8.2移除;也可PURGE BINARY LOGS TO 'mysql-bin.000123'手动清理 - 查看:
mysqlbinlog解析查看(--base64-output=DECODE-ROWS -v可把ROW事件还原为可读SQL) PITR操作示例:- 先恢复最近一次全量备份
- 再重放备份时间点之后的
binlog增量(可按时间或按位置)1 2 3 4mysqlbinlog --start-datetime="2025-01-01 00:00:00" \ --stop-datetime="2025-01-01 12:00:00" \ mysql-bin.* | mysql -uroot -p # GTID 环境重放建议加 --skip-gtids
GTID关联:复制可用GTID(全局事务标识)替代基于文件+位置的定位,更可靠;8.0默认关闭、8.4起默认开启
- 开启与格式:
协助记忆
binlog像快递公司的底单流水——每件货(每个事务)都留底,能按单号(position/时间点)查到发过什么、补发(恢复)到哪一步,还能把底单同步给分站(从库);redo log则像仓库自己墙上的流水账——写错了要能当场涂改恢复,账本小、只留最近一段,和底单(binlog)对得上才敢说这单真发出去了- 口诀:复制恢复靠
binlog,崩溃恢复靠redo,两阶段提交保一致
进阶思考
binlog会丢吗?sync_binlog=1且配合innodb_flush_log_at_trx_commit=1时,已提交事务不会从binlog丢失;调低刷盘频率(0或N>1)是以性能换"最多丢最近一批未刷盘事务"的风险
两阶段提交到底解决什么问题?
- 解决"
binlog写了但InnoDB没提交"或反过来的不一致。崩溃恢复时以binlog为准——写了binlog的prepared事务就提交,没写的就回滚,保证主库数据与binlog完全对齐,复制和恢复才不会错乱
- 解决"
为什么
8.0要把默认格式改成ROW并逐步弃用binlog_format?STATEMENT在非确定性函数、触发器等场景主从结果不可控,ROW从根本上消除不一致;代价是日志变大,用事务压缩和binlog_row_image=MINIMAL缓解
误删了整张表怎么恢复?
- 找误删前的全量备份恢复到临时实例,再用
mysqlbinlog重放binlog到误删前一刻(--stop-datetime或--stop-position),导出数据导回线上;前提是binlog保留期覆盖了误删时间点——所以保留时长要按业务留够
- 找误删前的全量备份恢复到临时实例,再用
扩展信息
Atomic DDL(8.0):数据字典更新、存储引擎操作与binlog写入合并为单个原子事务,DDL要么全部成功要么全部回滚,不再出现"表建了一半"或binlog与元数据不一致Group Commit:多个事务按提交组批量写binlog并一次性fsync,sync_binlog=N的单位就是提交组,兼顾吞吐与持久性binlog加密:8.0.14起binlog_encryption可加密binlog与relay log;开启后mysqlbinlog无法直接读取binlog文件,需改用--read-from-remote-server从实例拉取,第三方解析工具同样受影响binlog提取SQL的工具:mysqlbinlog(官方自带,可离线) :mysqlbinlog --base64-output=DECODE-ROWS -v mysql-bin.000123把ROW事件解码为###开头的伪SQL(UPDATE显示### WHERE前映像 +### SET后映像),-vv追加列类型注释;注意解码后列名显示为@N序号(原始列名丢失),且伪SQL仅供人读/审计、不可直接重放——真正可重放的是默认输出的base64 BINLOG '...'语句(管道交给mysql执行,需BINLOG_ADMIN/SUPER权限)binlog2sql(Python开源) :把binlog逆向生成原始SQL(INSERT/UPDATE/DELETE),--flashback(-B)生成回滚SQL实现闪回;兼容Python 2.7/3.4+,原版已测试MySQL 5.6/5.7(8.0需依赖社区fork);必须连接在线MySQL(经BINLOG_DUMP协议拉取并读information_schema元数据),需SELECT,REPLICATION SLAVE,REPLICATION CLIENT权限my2sql(Go开源) :基于go-mysql解析库,-work-type 2sql|rollback|stats分别提取SQL/ 生成回滚SQL/ 统计执行量,-sql insert,update,delete过滤类型、-file-per-table按表拆分、-big-trx-row-limit定位大事务;-mode file可离线解析binlog文件;8.0可用但需mysql_native_password认证;回滚要求ROW+FULL,无主键表需-full-columnsCDC工具(Canal/Maxwell/Debezium) :伪装从库实时拉取binlog,输出行级变更事件流(Canal为自定义客户端协议、Maxwell输出JSON、Debezium为跨库通用框架),不还原原始SQL文本,适合实时同步/异构数据管道- 闪回前提(两工具共同限制) :
binlog_format=ROW且binlog_row_image=FULL(官方README均明示,不支持MINIMAL);只能回滚DML,DDL不可回滚;大事务回滚性能差,且解析段内夹杂DDL(表结构变更)会导致回滚异常 - 选型对比:应急闪回选
binlog2sql(需在线库)/my2sql(可离线);离线审计、还原用mysqlbinlog;实时订阅选CDC工具;原版binlog2sql与my2sql均已多年无实质更新,生产使用需评估停更风险;8.0.20+事务压缩(binlog_transaction_compression)下第三方解析工具的兼容性需现场验证
🤔 SHOW PROCESSLIST 命令有什么作用?
SHOW PROCESSLIST用于查看MySQL服务器当前各客户端连接线程及部分系统线程的运行状态,是回答"数据库现在在干什么、谁在跑、跑了多久、卡在哪"的第一入口命令。第一层:命令形式与输出
- 语法:
SHOW [FULL] PROCESSLIST;FULL表示显示完整语句文本,不加时语句会被截断。 - 输出列(每行代表一个线程):
Id:连接标识符,与CONNECTION_ID()、performance_schema.threads的PROCESSLIST_ID对应。User:发出语句的MySQL用户。system user表示内部非客户端线程(如复制I/O与SQL线程);unauthenticated user表示尚未完成认证的连接;另有event_scheduler表示事件调度线程。Host:客户端主机,TCP连接显示为host:port。db:线程当前默认数据库,可为NULL。Command:线程正在执行的命令类型,空闲连接为Sleep。Time:线程处于当前状态的秒数。State:线程当前正在做什么的动作/状态。Info:正在执行的语句;无语句时为 NULL。
Info截断规则:不加FULL时Info仅显示语句前100个字符;SHOW FULL PROCESSLIST在默认实现下显示完整语句。
- 语法:
第二层:权限要求
- 拥有
PROCESS权限:可查看所有线程,包括属于其他用户的线程。 - 无
PROCESS权限:非匿名用户只能看到自己的线程;匿名用户看不到任何线程信息。 - 生产实践:监控账号通常授予
PROCESS权限,才能在全库范围定位
- 拥有
第三层:典型用途与排障手法
- "
too many connections" 排障:连接数占满时,MySQL为拥有CONNECTION_ADMIN(或已废弃的SUPER)权限的账户保留一条额外连接,DBA仍能连入执行本命令诊断。 - 定位慢查询/卡住线程:结合 Time(当前状态持续秒数)与 State 态持续数秒不变化,通常值得关注——但先结合 Command 排除正常的Sleep 空闲态,并留意 Waiting for … 类阻塞态(如 Waiting for table metadata lock)。
- 终止线程:KILL
按 Id 终止;自己的线程可直接终止,终止 MIN(或已废弃的 SUPER)权限。
- "
第四层:· 的演进
- 废弃路径:
INFORMATION_SCHEMA.PROCESSLIST表自8.0.22起标记废弃(deprecated),在8.4 LTS中仍保留但官方建议迁移。 - 新实现:
8.0.22起新增performance_schema.processlist表;设ocesslist=ON可让SHOW PROCESSLIST与mysqladmin processlist改用它(默认OFF,仍走旧实现)。 - 新实现的好处:查询过程不持全局互斥锁——旧实现遍历线程时持有 。
- 其他替代:
performance_schema.threads表能显示SHOW PROCESSLIST看不到的后台线程;sys.processlist与sys.session是面向人类阅读的便捷视图(8.0.22+底层已基于processlist表)。
- 废弃路径:
协助记忆
SHOW PROCESSLIST就像护士站的大屏 ——— 每个病人(连接/查询)显示姓名(User)、床位(Host)、所在科室(db)、在做什么(Command/State) 、待了多久(Time)、诊断记录(Info)。默认大屏只放一行摘要,SHO整病历。- 口诀:
PROCESS看全库线程,FULL看完整SQL,KILL按Id处置。
进阶思考
Info默认被截断,想看完整SQL怎么办?- 用
SHOW FULL PROCESSLIST;或查INFORMATION_SCHEMA.PROCESSLIST的INFO列(类型varchar(65535),不截断)——但该表已废弃。注意若启用PS新实现,Info上限为1024字符,超长语句仍会被截。
- 用
- 如何区分"真卡住"和"正常慢"的查询?
- 看
State是否长期停留在某非Sleep状态且Time持续增 驻几乎总是问题;Sending data、Copying to tmp table这类执行中状态长驻则要先判断语句本身是否低效。定位到Id后可KILL <Id>。
- 看
SHOW PROCESSLIST与performance_schema.threads有何区别- 旧版
SHOW PROCESSLIST遍历线程时持全局互斥锁,高并发下有开销;threads表无锁且能看到后台线程;8.0.22起可用performance_schema.processlist获得无锁的等价物。
- 旧版
- 能杀掉其他用户的线程吗?
- 需要
CONNECTION_ADMIN(或已废弃的SUPER)权限,否则<Id>只终止当前语句、KILL <Id>断开整个连接。
- 需要
扩展信息
MariaDB兼容:MariaDB同样支持SHOW [FULL] PROCESSLIST,输出列与Info默认截断为100字符的行为一致。- 常见误区:
Time很大的Sleep线程不代表问题——那是空闲连接;T非语句总耗时。 - 命令行等价物:
mysqladmin processlist与不加FULL等价(同样截断);mysqladmin --verbose processlist才等价于SHOW FULL PROCESSLIST。
🤔 Innodb 缓冲池(innodb_buffer_pool_size)作用与调优思路?
InnoDB缓冲池(buffer pool)是MySQL在内存中缓存表数据页与索引页的主缓存,是InnoDB读取性能的核心。调优本质是:给够内存(池越大越像内存数据库)、留足余量(不触发换页)、选对实例数与预热策略、用命中率验证。第一层:缓冲池是什么、缓存什么
- 定位:
InnoDB在内存中划分的区域,缓存被访问过的表数据页和索引页;后续读取直接命中内存,避免每次访问磁盘。 - 页面与链表:缓冲池按页(
page)管理,一页可容纳多行;用LRU变体组织——新页插入链表中部,把链表分为young(新)和old(旧)两个子链表;默认old子链表占缓冲池的3/8。 - 命中率含义:池越大,InnoDB 越像内存数据库——数据从磁盘读一次后,后续访问全在内存。
- 边界澄清:
change buffer(二级索引变更缓冲)与自适应哈希索引(AHI)是逻辑上独立的机制,但它们的内存都来自缓冲池,调优时与缓冲池共享同一份内存预算,不要理解为独立的内存区域。
- 定位:
第二层:默认值与版本相关行为(MySQL 8.0)
- 默认大小:
innodb_buffer_pool_size默认128MB(134217728字节)。 - 自动配置:
innodb_dedicated_server(默认OFF)开启后,在专用服务器/容器上按检测到的物理内存自动计算缓冲池:内存<1GB→128MB;1GB~4GB→ 内存 × 0.5; >4GB→ 内存 ×0.75。它不只调缓冲池,还会自动配置redo log容量与flush_method等。 - 分片:
innodb_buffer_pool_instances把池分成多个区域减少锁竞争;默认值:池≥1GB时为8,<1GB时为1;上限64;该选项仅在池≥1GB时生效;官方建议每个实例≥1GB。 - 块大小:
innodb_buffer_pool_chunk_size默认128MB,是池的动态调整单位;仅启动时可改(1MB步长),运行期不可改。
- 默认大小:
第三层:动态调整
- 在线扩容/缩容:
SET GLOBAL innodb_buffer_pool_size = 1073741824; 由后台线程逐步执行,无需重启;调整前需等待活动事务完成,调整期间需要访问缓冲池的新事务会等待。 - 倍数约束:新值必须是
chunk_size × instances的倍数,否则自动向上取整到下一个合法倍数(如16实例、128MB块,请求9G→ 实际10G)。改chunk_size或instances需重启。 - 注意事项:改大前先确认物理内存余量,避免换页;大池调整耗时较长,可观察
Innodb_buffer_pool_resize_status(8.0.31起含status_code/progress)。
- 在线扩容/缩容:
第四层:调优思路(评估 → 定大小 → 验证 → 防退化)
- 先看当前命中率:
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%'; 用(1 - Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests) × 100估算;健康目标99%+,长期低于95%说明池偏小(两个变量是近似口径,仅作趋势参考)。 - 定大小:官方文档指出"专用服务器上常把物理内存的
80%分给缓冲池";同时要留足内存给MySQL其他结构(线程栈、排序/连接缓冲等)和操作系统,避免触发换页。50%~75%是常见工程经验值(建议,非官方规定)。 - 专用机/容器:直接
innodb_dedicated_server=ON让服务器按内存自动配置。 - 选实例数:池达多
GB时用多实例减少锁竞争,每个实例≥1GB;一般从"每1GB一个实例"起步(建议)。 - 防缓存污染:大批量扫描(
mysqldump、无WHERE的全表扫描)会用无用页冲刷热数据;用innodb_old_blocks_time让大扫描读入的数据快速老化、不被提升为热页。 - 重启预热:开启
innodb_buffer_pool_dump_at_shutdown + innodb_buffer_pool_load_at_startup,关库时把热页元数据落盘、启动时加载,避免每次重启后冷启动命中率骤降;8.0.17起dump默认只落最近25%热页,大池可调大innodb_buffer_pool_dump_pct。
- 先看当前命中率:
协助记忆
- 缓冲池就像厨房的备菜台。客人(查询)点菜,厨师把常点的菜提前备在台面,取菜(命中内存)远快于现去冷藏库翻(读磁盘)。台子越大,常点菜越能摆下,InnoDB 就越像"全在台面上"的内存餐厅。调大 size 就是扩备菜台——但扩太大会把走廊(操作系统/其他进程内存)挤没,人过不去(换页)。
- 口诀(一句):Buffer Pool 越大越像内存库,命中率 99 起步;调大先看内存余量,chunk×instances 是倍数。
进阶思考
- 为什么
buffer pool很大但命中率还是低?- 可能热数据集(
working set)已超出池容量;也可能缓存被扫描/批量任务污染;或读取模式本身不友好(大量全表扫描)。先看INNODB_BUFFER_POOL_STATS与缓冲池页构成,必要时用innodb_old_blocks_time治理污染,而不是一味加大。
- 可能热数据集(
- innodb_buffer_pool_size 能设多大?
64位系统上限很大,但实际受物理内存约束:缓冲池 + 其他内存结构 + 操作系统必须留在物理内存内,否则换页会让性能崩盘。官方给出的80%是经验指引,不是强制上限。
- 在线调整池大小会阻塞业务吗?
- 调整由后台线程执行,但开始时须等活动事务结束,调整期间需要访问缓冲池的新事务会等待;池越大耗时越长。生产环境建议低峰操作,并观察
Innodb_buffer_pool_resize_status。
- 调整由后台线程执行,但开始时须等活动事务结束,调整期间需要访问缓冲池的新事务会等待;池越大耗时越长。生产环境建议低峰操作,并观察
- 实例数设多少合适?
- 池
≥1GB时默认8;每个实例建议≥1GB。实例过少在多核高并发下锁竞争明显,过多则单实例过小无收益。一般从"每1GB一个实例"起步(建议),上限64。
- 池
- 为什么
扩展信息
- 概念辨析:
change buffer把二级索引的变更先缓存起来、延迟合并(待对应页读入缓冲池时再合并),其数据落盘于系统表空间、跨重启存活;自适应哈希索引(AHI)加速等值查询。三者机制各自独立,但AHI内存直接取自缓冲池、change buffer页也缓存于缓冲池,调优时占用同一块缓冲池内存预算。 MariaDB差异:MariaDB同样使用innodb_buffer_pool_size,机制类似(同为LRU分片),但默认值与自动配置逻辑与MySQL不同,跨发行版迁移时按所用版本确认。- 相关指标:
SHOW ENGINE INNODB STATUS的BUFFER POOL AND MEMORY段直接给出 “Buffer pool size/Free buffers/Database pages/Buffer pool hit rate";information_schema.INNODB_BUFFER_POOL_STATS按POOL_ID每实例一行,逻辑读请求对应NUMBER_PAGES_GET列。 - 关于
innodb_buffer_pool_size参数的配置,在混合环境(代理、应用、数据库)下,根据个人经验,在非高并发场景下,可以尝试设置为总内存一半的一半的75%, 即总体内存的 18.75% ,以确保服务器的稳定运行。
- 概念辨析:
🤔 performance_schema 数据库有什么作用?
performance_schema是MySQL内置的服务端运行时性能监控数据库,用"插桩 + 事件采集"记录服务器内部底层活动——等待事件、语句执行、事务、内存、锁与I/O等,供性能分析与排障使用。它不是存业务数据的库,而是常驻内存的"服务器体检仪表盘”。第一层:它是什么、与普通库的区别
- 性质:名为
performance_schema的schema,底层由PERFORMANCE_SCHEMA存储引擎实现;表全在内存,重启即清空,不持久化、不写binlog、不参与复制。 - 定位分工:
performance_schema回答"服务器正在发生/发生过什么"(运行时性能数据);information_schema回答"库里有什么对象"(元数据)。两者职责不同,不要混用。 - 版本起源(基线 5.x):
5.5.3引入,默认关闭;5.6.6起默认开启(5.6.0~5.6.5仍默认关闭);5.6加入语句事件与digest汇总;5.7默认开启,并补全事务事件、内存统计、metadata_locks表、sys schema封装(sys随 5.7 默认安装)。
- 性质:名为
第二层:它采集哪些数据(5.7 基线)
- 等待事件
events_waits_*:线程在等待什么(磁盘I/O、锁、互斥量、socket等),是定位瓶颈的关键信号。 - 语句事件
events_statements_*:SQL的执行耗时、扫描行数、返回行数、错误数;可按digest/用户/主机/账号汇总(events_statements_summary_by_digest等)。 - 阶段事件
events_stages_*:语句执行的不同阶段(解析、排序等)。 - 事务事件
events_transactions_*:事务状态与耗时。 - 内存统计
memory_summary_*:各instrument/线程/账号的当前与峰值内存。 - 锁与
I/O:metadata_locks(元数据锁)、table_handles、table_io_waits_summary_*、file_summary_*。 - 连接与线程:
threads、accounts/hosts/users等维度汇总。
- 等待事件
第三层:怎么用它排障(核心用法)
- 看当前在跑什么:
SELECT * FROM performance_schema.events_statements_current\G - 找慢语句
Top:SELECT DIGEST_TEXT, COUNT_STAR, SUM_TIMER_WAIT FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;(SUM_TIMER_WAIT单位为皮秒) - 定位瓶颈维度:先看语句耗时,再看等待事件落到哪类(
I/O、锁、还是CPU);events_waits_summary_global_by_event_name看累计等待大头。 - 查元数据锁:
SELECT * FROM performance_schema.metadata_locks\G找谁持有/在等MDL(DDL阻塞排查)。注意前提:5.7中支撑该表的插桩wait/lock/metadata/sql/mdl默认关闭,直接查会一直为空,需先开启——启动参数performance-schema-instrument='wait/lock/metadata/sql/mdl=ON',或运行时UPDATE performance_schema.setup_instruments SET ENABLED='YES' WHERE NAME='wait/lock/metadata/sql/mdl';(8.0起该插桩默认开启)。 - 内存大头:
SELECT * FROM performance_schema.memory_summary_global_by_event_name ORDER BY CURRENT_NUMBER_OF_BYTES_USED DESC LIMIT 10; - 便捷视图:
sys schema提供sys.session、sys.schema_table_lock_waits、sys.statement_analysis等封装,少写join(同样依赖上述mdl插桩时先开启)。
- 看当前在跑什么:
第四层:启用与开销
- 默认开启:
5.7/8.0默认ON;5.6需5.6.6+才默认ON。主开关与performance_schema_max_*容量变量非动态,改配置需重启;插桩开关本身可动态改(setup 表)。 - 开销:常驻内存 + 少量
CPU;用performance_schema_max_*控制各表容量上限;不需要的插桩可在setup_instruments/setup_consumers中动态关闭。 - 影响面:
5.7默认配置下内存通常几十~几百 MB(经验值,非官方规定),高并发实例按需调上限;监控收益通常远大于开销。
- 默认开启:
协助记忆
performance_schema像车内的行车记录仪——不断记录车辆运行中的各种事件,比如“踩油门、刹车、等红灯”(对应 SQL、等待、阶段等事件),出问题时可以查看当时发生了什么、在等什么。**业务表是车上的货,负责存储业务数据;performance_schema 是行车记录仪,负责记录数据库运行状态。**关闭某些插桩,就像关闭记录仪的部分传感器:对应的监控数据就不会被采集。- 口诀:
PS管"发生了什么",I_S管"有什么";慢语句看digest,瓶颈看等待。
进阶思考
- ``performance_schema
和information_schema到底什么区别?I_S是对象元数据(表结构、列、权限等"静态存在");PS是运行时性能数据(“动态发生”)。早期I_S里也有PROCESSLIST这类偏性能的表,职责上现已明确分离。
- 为什么说它是"内存库"?重启数据会丢吗?
PERFORMANCE_SCHEMA引擎的表全在内存,启动时重建、关库即弃,不持久化。只有开关和容量上限这类配置写在配置文件里才持久。
- 5.6 和 5.7 用起来最大差别是什么?
5.6.6前默认关闭需显式开启;5.7默认开启,且多了事务事件、内存统计、metadata_locks、digest汇总和sys封装,基本开箱即用。
- 开启会拖慢服务器吗?
- 有内存和少量
CPU开销,可通过performance_schema_max_*限内存、用setup_instruments/setup_consumers关不需要的插桩;对绝大多数实例收益远大于开销。
- 有内存和少量
- ``performance_schema
扩展信息(8.x 作为扩展点)
8.0:新增variables_info(变量类型/作用域)等表;8.0.22起新增performance_schema.processlist表,作为SHOW PROCESSLIST的可选底层实现(performance_schema_show_processlist=ON,免全局互斥锁);该能力也回补到了5.7.39+(升级安装时5.7侧需按手册手工建表,新装则自动创建)。8.0起部分表与插桩行为有调整(如 mdl 插桩默认开启),具体以对应版本手册为准。MariaDB也有performance_schema(自己的版本时间线),跨发行版按文档确认。
🤔 MySQL 出现大量 Sleep 线程是什么原因?如何优化?
Sleep线程是PROCESSLIST中Command=Sleep的空闲连接——本身几乎不干活,但会占连接槽位、掩盖长事务和连接泄漏。大量出现几乎都是"应用连接池配置不当 +MySQL空闲超时过大 + 代码连接未回收"叠加的结果,治理要"应用侧回收 +MySQL侧超时 + 监控治理"三管齐下。第一层:先分清
Sleep线程的正常与异常- 什么是
Sleep:PROCESSLIST中Command=Sleep表示该连接当前无正在执行的语句,处于空闲状态 - 正常场景:连接池预热、请求间隙的空闲连接,少量
Sleep完全正常 - 异常场景:数量大且长时间(如
Time列持续几分钟到几小时)不释放——连接池空闲连接堆积、连接泄漏、或事务挂起 - 关键误区:执行中的慢查询在
PROCESSLIST里是Command=Query,不是Sleep;Sleep持锁的真正来源是长事务未commit/rollback、在语句执行间隙挂起,这种连接同时持有行锁/元数据锁,会阻塞其他事务
- 什么是
第二层:大量
Sleep的原因- 应用连接池配置不当(最常见) :最大连接数设得过大(远高于业务并发)、空闲连接不回收(回收间隔未配或配置过大)、连接池预热创建了大量连接
MySQL参数过大:wait_timeout/interactive_timeout默认都是28800(8小时),空闲连接长时间不被服务端断开- 连接泄漏:应用代码开了连接未 ·(异常分支未释放、ORM 未正确关闭),连接只增不减
- 长事务/未提交事务:事务开启后迟迟不提交,连接在语句间隙一直显示
Sleep并持锁 - 超时配置失配:应用侧超时(如
JDBC的socketTimeout、连接池maxLifetime)与MySQL的wait_timeout没有联动,两边都不主动断
第三层:危害有多大
- 占连接槽位:每个
Sleep连接占一个max_connections名额(默认151,5.7/8.0相同),打满后新连接报Too many connections;MySQL会额外保留 1 个连接给CONNECTION_ADMIN/SUPER权限账户,供管理员应急登录 - 占用线程与内存:非线程池模式下每连接独占一个线程及其栈内存、net buffer(thread pool 插件是 MySQL 企业版功能,社区版没有),
Sleep数量极大时内存/线程开销不可忽略 - 持锁阻塞:事务未提交的
Sleep连接持有的行锁/元数据锁不释放,会让其他事务陷入LOCK WAIT,甚至拖垮整个实例
- 占连接槽位:每个
第四层:如何诊断
- 查看连接状态:
1 2 3 4 5 6 7SHOW FULL PROCESSLIST; -- 看 Command=Sleep、Time、Host、Info SELECT id, user, host, db, command, time, state FROM information_schema.processlist WHERE command = 'Sleep' ORDER BY time DESC; -- 统计 Sleep 线程 SHOW STATUS LIKE 'Threads_connected'; -- 当前连接数 SHOW STATUS LIKE 'Threads_running'; -- 正在执行的线程数 SHOW VARIABLES LIKE 'max_connections'; -- 连接上限 - 按来源聚合:对
processlist按host分组统计,定位是哪个应用/IP产生的大量连接 - 揪出事务中的
Sleep:1 2SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id, trx_rows_locked FROM information_schema.innodb_trx; -- 与 processlist.id 关联 - 查询
processlist与innodb_trx需要PROCESS权限;8.0.22起可改用performance_schema.processlist表或sys.session视图,诊断更全面
- 查看连接状态:
第五层:如何优化(MySQL 侧)
- 调小
wait_timeout:把空闲连接超时调到合理范围(如60~300秒,经验值,需结合业务判断),让MySQL主动断开空闲连接:1 2SET GLOBAL wait_timeout = 60; -- 立即生效,但只对新连接有效 -- 持久化:写入 my.cnf [mysqld] 段 wait_timeout只作用于非交互连接(应用连接);交互连接(mysql客户端、Workbench)由interactive_timeout控制,治理时两者要一起看- 副作用:调得过小会把连接池的空闲连接掐断,客户端可能报
server has gone away——— 必须和连接池的空闲检测/keepalive间隔匹配(连接池侧回收时间要小于服务端wait_timeout) thread_cache_size:缓存已断开连接的空闲线程供复用,减少频繁建连的线程创建开销(作用于线程而非连接,与 Sleep 治理是间接关系)
- 调小
第六层:如何优化(应用侧,治本)
- 连接池合理配置:最大连接数与业务真实并发匹配,不盲目调大;配置空闲回收与有效性检测。以主流连接池为例:
HikariCP:maximumPoolSize(不宜过大)、idleTimeout、maxLifetime、connectionTestQueryDruid:maxActive、minIdle、timeBetweenEvictionRunsMillis(回收扫描间隔)、testWhileIdle、validationQuery
- 代码正确释放连接:
try-with-resources / finally中close,异常路径同样释放;定期代码审查排查泄漏 - 联动超时:应用连接池的
maxLifetime/idleTimeout与MySQL wait_timeout保持"应用侧更短"的关系,避免两边都不回收
- 连接池合理配置:最大连接数与业务真实并发匹配,不盲目调大;配置空闲回收与有效性检测。以主流连接池为例:
第七层:治理与监控
- 谨慎
kill:先查innodb_trx确认没有活动事务,再kill纯空闲连接;KILL QUERY只终止正在执行的语句、保留连接(比KILL CONNECTION温和);KILL(等价KILL CONNECTION)终止连接及其语句 - 工具化清理:
Percona Toolkit的pt-kill --match-command sleep可按条件批量杀空闲连接,注意排除事务中的连接 - 监控告警:监控
Threads_connected/max_connections比率(如>80%告警),同时看Threads_running差值识别空闲占比,避免夜间低峰误报;连接池侧同步监控活跃/空闲连接数
- 谨慎
协助记忆
MySQL像停车场,Sleep线程就是占着车位不开走的车。车(连接)本身不费油,但车位(max_connections)是有限的;真正危险的是占着车位还锁着方向盘的"事务车"(持锁),以及越来越多有进无出的"僵尸车"(连接泄漏)。治理就是三件事:停车场限时(wait_timeout调小)、车主自觉挪车(应用连接池回收 + 代码关连接)、保安盯监控(Threads_connected告警)- 口诀:连接池回收治本,
wait_timeout兜底,先查事务再kill
进阶思考
wait_timeout调到120后,为什么已有的Sleep连接还不断?- 会话的
wait_timeout在连接建立时从全局值初始化,改全局只影响之后新建的连接;要立即清理存量,得kill或等连接池回收
- 会话的
Sleep线程和慢查询是一回事吗?- 不是。慢查询在执行中(
Command=Query、state有值),Sleep是空闲(无语句在执行);但长事务会在语句间隙显示Sleep且持锁,两者要分开排查——前者看慢查询日志,后者查innodb_trx
- 不是。慢查询在执行中(
为什么
Sleep线程打满连接后还能登录?MySQL会额外保留一个连接名额给CONNECTION_ADMIN/SUPER账户,连接打满时管理员仍可登录杀连接、调整参数——这也是应急入口必须留好的原因
连接池最大连接数设多大合适?
- 不是越大越好。连接数远超并发只会堆出一堆
Sleep占用槽位;经验上结合Threads_running观察真实并发,按业务峰值留余量即可,宁可排队也不要无限放大
- 不是越大越好。连接数远超并发只会堆出一堆
扩展信息
- 默认认证插件:
8.0起默认caching_sha2_password,旧驱动/中间件连接8.0报认证失败很常见(容易被误判为连接问题),需升级驱动;5.7默认是mysql_native_password - 诊断入口升级:
8.0.22起SHOW PROCESSLIST的数据来源改为performance_schema.processlist表,可配合sys.session视图做更精细的连接/事务诊断 KILL语法:KILL [CONNECTION | QUERY]语法5.7已存在,并非8.0新增- 线程模型:
Thread Pool(线程池插件)在5.7与8.0均为MySQL企业版功能,社区版仍是每连接一线程
- 默认认证插件:
🤔 MySQL 查询慢,如何排查?
查询慢排查是一条"确认现象 → 慢日志定位 →
EXPLAIN看计划 → 分层找根因 → 针对性优化"的漏斗式链路;绝大多数慢查询根因集中在SQL写法与索引(约占八成),其余是锁等待、服务器资源与配置。先判断"全库慢还是单条慢、偶发还是持续",再逐层下钻,避免一上来就调参数。第一层:排查总思路(先定性再定量)
- 确认现象:是单条
SQL慢、某个业务接口慢,还是全库整体变慢?偶发一次还是持续?偶发优先查锁等待与资源抖动,持续优先查索引与SQL写法 - 漏斗式排查:现象 → 慢查询日志定位具体
SQL→EXPLAIN分析执行计划 → 按"SQL写法 / 索引 / 锁 / 服务器资源"分类定位根因 → 针对性优化 → 验证效果
- 确认现象:是单条
第二层:开启慢查询日志定位问题
SQL参数配置:
1 2 3 4 5[mysqld] slow_query_log = ON # 慢日志开关(5.7/8.0 默认关闭) long_query_time = 2 # 超过 2 秒的记录(默认 10 秒,按业务调整) log_queries_not_using_indexes = ON # 记录未走索引的查询(可选,默认 OFF) log_throttle_queries_not_using_indexes = 10 # 同类未用索引日志限流,防日志暴涨慢日志开启有
IO代价,且语句在执行完、锁释放后才写入,日志顺序不等于执行顺序分析工具:
1 2mysqldumpslow -s c /var/lib/mysql/*-slow.log # 官方自带,按次数排序聚合 pt-query-digest /var/lib/mysql/*-slow.log # Percona Toolkit(第三方,需安装),分析更细
第三层:
EXPLAIN分析执行计划基本用法:
EXPLAIN SELECT ...(不实际执行语句,只给计划;5.7/8.0均支持EXPLAIN FORMAT=JSON看成本细节)重点看四列:
type:访问类型,从优到劣大致system>const>eq_ref>ref>range>index>ALL;出现ALL(全表扫描)通常就是慢的直接原因key:实际使用的索引;key为空说明没走索引rows:估算扫描行数(注意是估算值,偏差大时先ANALYZE TABLE更新统计信息)Extra:Using filesort(文件排序)、Using temporary(临时表)、Using index(覆盖索引,好)
统计信息校正:优化器依赖索引基数统计,数据量变化大导致走错索引时,
ANALYZE TABLE表名 可更新统计
第四层:常见根因分类
SQL写法与索引失效:无索引;隐式类型转换(如字符串列与数字比较);函数包裹索引列(WHERE DATE(create_time)=...无法用索引);联合索引未用前导列;LIKE '%xxx'前置通配符;OR条件多数场景失效(但两侧列各有索引时可能走index_merge,视索引而定)- 深分页:
LIMIT 100000, 20要扫描并丢弃前10万行;优化用延迟关联(先取主键再回表)或游标分页(基于上一页最后一条的id条件) - 排序与临时表:
Using filesort / Using temporary出现时,检查sort_buffer_size、tmp_table_size/max_heap_table_size是否过小导致落盘;5.7.6+磁盘临时表默认用InnoDB - 锁等待:行锁、元数据锁(
MDL)、表锁阻塞查询。查看手段:information_schema.innodb_trx看事务、performance_schema.metadata_locks看MDL(5.7+)、死锁看SHOW ENGINE INNODB STATUS 的 LATEST DETECTED DEADLOCK段 - 服务器资源:
CPU高(SQL计算密集/未走索引)、磁盘IO慢(随机读放大)、buffer pool命中率低、swap、网络延迟
第五层:服务器与配置层面排查
- 看执行中的
SQL:SHOW PROCESSLIST / SHOW FULL PROCESSLIST,观察state(如Sending data、Waiting for table metadata lock) - 看
InnoDB状态:SHOW ENGINE INNODB STATUS,关注事务、锁等待、死锁段 - 看
buffer pool命中率:1 2 3SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests'; -- 逻辑读(命中 + 未命中) SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads'; -- 从磁盘读的次数 -- 命中率 ≈ (read_requests - reads) / read_requests,过低说明 innodb_buffer_pool_size 偏小 - 看系统层:
top(CPU/内存)、iostat(磁盘IO)、vmstat(上下文切换/swap),定位是SQL慢还是资源不足 - 配置热点:
innodb_buffer_pool_size(官方建议约为物理内存50%~75%)、sort_buffer_size、tmp_table_size/max_heap_table_size
- 看执行中的
第六层:优化手段汇总
- 索引优化:加/改索引、覆盖索引(把查询列并入索引避免回表)、联合索引按"等值在前、排序/范围在后"设计
SQL改写:避免SELECT *、避免函数包裹索引列、深分页改游标/延迟关联、大IN列表拆分- 数据治理:冷数据归档、历史表拆分、大事务拆小
- 架构层面:读写分离、分库分表(量大到单实例扛不住时)
- 参数调优:
buffer pool、排序/临时表相关参数,改完压测验证
协助记忆
- 慢查询排查像导航提示"前方拥堵"后重新规划 ——— 先看走没走高速(
type是否ref/range而不是ALL)、有没有绕远路(rows估算)、是不是红绿灯卡死(锁等待)、还是整条路车太多(服务器资源)。80%的情况是"导航没走高速"——索引问题,先看索引再看车流 - 口诀:慢日志定位、
EXPLAIN看路、索引为主、锁和资源兜底
- 慢查询排查像导航提示"前方拥堵"后重新规划 ——— 先看走没走高速(
进阶思考
EXPLAIN显示走了索引,为什么还是慢?- 走了索引不等于最优:可能
rows估算偏差(统计信息过时,先ANALYZE TABLE)、回表次数多(改覆盖索引)、深分页、排序/临时表(看Extra)、或数据分布不均导致优化器选错索引
- 走了索引不等于最优:可能
偶发慢查询怎么排查?
- 持续慢查索引,偶发慢优先查锁等待与资源抖动——看当时有没有大事务、
DDL(MDL阻塞)、磁盘IO尖峰、备份/大查询撞车;performance_schema与慢日志时间戳可回看
- 持续慢查索引,偶发慢优先查锁等待与资源抖动——看当时有没有大事务、
为什么"
OR 条件“不建议写?- 多数场景
OR导致索引失效走全表扫描;但两侧列各有独立索引时可能触发index_merge合并索引,所以准确说法是"视索引情况,多数场景失效”——稳妥做法是拆成UNION或改IN
- 多数场景
表数据量大了之后全表扫描无可避免吗?
- 单表千万级以内靠索引+覆盖索引通常够用;再大考虑冷数据归档、分区表、分库分表;同时监控
buffer pool与磁盘IO,避免内存装不下热点数据
- 单表千万级以内靠索引+覆盖索引通常够用;再大考虑冷数据归档、分区表、分库分表;同时监控
扩展信息
EXPLAIN ANALYZE(8.0.18+):实际执行语句并输出每个算子的真实耗时与行数,比估算的EXPLAIN更直观;8.0还支持EXPLAIN FORMAT=TREE- 直方图(8.0 引入) :
ANALYZE TABLE t UPDATE HISTOGRAM ON col手动为列建数据分布直方图,帮优化器对数据不均的列做出更准判断(5.7 无此能力) invisible index(8.0):可把索引设为不可见(ALTER TABLE ... ALTER INDEX ... INVISIBLE),先验证去掉该索引的效果再删除,零风险测试- 降序索引(8.0) :支持
INDEX (col DESC),解决"升序检索 + 降序排序"无法走索引的问题 hash join(8.0.18+):无索引等值连接自动启用哈希连接,替代旧版只能嵌套循环的低效执行- 函数索引(8.0.13+) :支持
INDEX ((DATE(create_time)))这类函数索引,缓解函数包裹列导致失效的问题 query cache已移除:5.7.20起废弃(query_cache_type默认OFF)、8.0彻底移除——别再指望它缓存结果
🤔 MySQL root 账号密码忘记怎么重置?
核心思路是"用特殊方式启动
MySQL绕过认证 → 登录后重置密码 → 恢复正常启动",主流两种方式:--skip-grant-tables(通用、应急)与--init-file(官方文档主推、更安全)。命令按 5.7 为主线给出,8.0 差异在扩展点单独说明——关键是别把 5.6/5.7 的UPDATE老写法用到 8.0 上。第一层:重置前须知(先看再动手)
- 高危操作:
root密码重置影响所有依赖该账号的连接,生产环境应走变更流程、评估停机窗口 - 分清版本:重置命令分三档 ——
5.6及更早(SET PASSWORD/ 写Password列)、5.7(ALTER USER或UPDATE authentication_string)、8.0(只能用ALTER USER) - 确认账号
host:实际账号可能是'root'@'localhost'也可能是'root'@'%',命令里的Host部分按SELECT user, host FROM mysql.user; 实际结果写 - 托管实例例外:云
RDS等托管实例不开放操作系统层,无法用本文方法,需走云厂商控制台重置
- 高危操作:
第二层:
方法一
--skip-grant-tables(通用应急)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17# 1) 停止 MySQL 服务 systemctl stop mysqld # systemd 环境;SysV 用 service mysql stop # 2) 跳过授权表启动;5.7 手动加 --skip-networking 防止无认证远程访问(8.0 会自动禁用远程连接) mysqld --skip-grant-tables --skip-networking & # 3) 无密码登录 mysql -uroot # 4) 在 mysql> 会话中执行: FLUSH PRIVILEGES; # 必须先刷新,否则 ALTER USER 被禁用 ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewPass@123'; # 5) 退出,恢复正常重启 exit mysqladmin -uroot -p shutdown # 或用 systemctl stop mysqld systemctl start mysqldFLUSH PRIVILEGES是必须的:--skip-grant-tables模式会禁用ALTER USER/SET PASSWORD等账号管理语句,刷新后才恢复若账号被锁定(
account_locked='Y'),先ALTER USER ... ACCOUNT UNLOCK
第三层:
- 方法二 –init-file(官方文档主推、更安全)
1 2 3 4 5 6 7 8 9 10 11 12 13# 1) 准备包含重置语句的 SQL 文件(属主设为 mysql 运行用户并限权,保证服务器可读) echo "ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewPass@123';" > /tmp/mysql-init.sql chown mysql:mysql /tmp/mysql-init.sql chmod 600 /tmp/mysql-init.sql # 2) 停止服务,带 init-file 启动(启动时自动执行文件里的语句) systemctl stop mysqld mysqld --init-file=/tmp/mysql-init.sql & # 3) 确认启动成功后删除临时文件,恢复正常重启 rm -f /tmp/mysql-init.sql mysqladmin -uroot -p shutdown systemctl start mysqld - 官方文档将
init-file列为主推方法,把--skip-grant-tables标注为"less secure“的替代——因为它不跳过授权检查,只执行一次指定语句
- 方法二 –init-file(官方文档主推、更安全)
第四层:
5.x老方法(UPDATE mysql.user,仅5.x可用)1 2 3 4 5 6 7 8 9-- 5.7 写法(注意:5.7 起密码存 authentication_string 列,且要同时清掉过期标记) UPDATE mysql.user SET authentication_string = PASSWORD('NewPass@123'), password_expired = 'N' WHERE User = 'root' AND Host = 'localhost'; FLUSH PRIVILEGES; -- 5.6 及更早写法(5.6 的 mysql_native_password 读 Password 列,写 authentication_string 不生效) UPDATE mysql.user SET Password = PASSWORD('NewPass@123') WHERE User = 'root' AND Host = 'localhost'; FLUSH PRIVILEGES;5.7中PASSWORD()函数已弃用但仍可用;mysql.user的Password列在5.7中并未删除,只是弃用并存,认证数据存authentication_string5.6与5.7写法不同:5.6写Password列,5.7 写authentication_string,别混用
第五层
- 验证结果
mysql -uroot -p -e "SELECT 1"# 交互式输密码,避免明文出现在进程列表和 shell 历史
- 若开启
validate_password插件,弱密码会让ALTER USER直接报错,需按密码策略设置
- 验证结果
协助记忆
--skip-grant-tables像撬锁进门——安保系统(授权表)整个关了才能进,进去后要先把安保系统重启(FLUSH PRIVILEGES)再换门锁密码(ALTER USER),走的时候记得把锁装回去;init-file像请物业带钥匙——不开门禁,只让管理员进去换一次锁(执行一条语句),更安全。8.0这个"新锁”(caching_sha2_password)换法特殊,不能再像5.6那样直接改登记表(UPDATE),必须走正规换锁流程(ALTER USER)- 口诀:先停库、跳过认证、刷新权限、
ALTER USER、正常重启
进阶思考
为什么
8.0不能用UPDATE mysql.user直接改密码?8.0默认认证插件caching_sha2_password的存储值是带摘要轮数的专用哈希,认证时做challenge-response校验;直接写入明文或错误格式必然校验失败(此为机制解释;官方文档在8.0已整体删除UPDATE方式,只保留ALTER USER)
skip-grant-tables启动时没加--skip-networking有什么风险?MySQL会监听网络端口且无认证,任何能连到该端口的机器都能免密登录 ——5.7需手动加,8.0会自动启用skip_networking;应急操作务必确认没有远程暴露
主从环境下重置
root密码要注意什么?- 重置的是本机账号,复制账户不受影响;但
skip-grant-tables停机窗口内复制线程停摆会累积延迟(relay log/GTID不受影响),重启后主从需重新追平
- 重置的是本机账号,复制账户不受影响;但
重置后密码不生效(还能用旧密码登录)?
- 多为
auth_socket/ 无密码认证插件场景(如Debian/Ubuntu默认root走auth_socket),此时ALTER USER改的密码不参与校验,需先ALTER USER ... IDENTIFIED WITH mysql_native_password BY '...'明确认证插件(8.0推荐caching_sha2_password)
- 多为
🤔 MySQL 插入中文乱码怎么解决?
乱码的本质是字符集在"客户端 → 连接 → 服务器 → 库 → 表/列"链路各层不一致,编码被错误转换或贴错标签。解决分三步:先定位是"显示乱"还是"数据坏",再按"新数据统一
utf8mb4、存量数据按字节实际编码转码修复"处理;5.7默认字符集latin1是历史坑,全链路统一utf8mb4是根治方案。第一层:先定位——显示问题还是存储问题
- 看存储字节:
SELECT id, HEX(name) FROM t; ——— 若HEX是完整正确的UTF-8字节序列(如"中"是E4 B8 AD),说明数据没坏只是显示乱,问题在客户端/终端/连接层;若字节本身是错的(如每个字只剩一个字节、变成?或乱字节),说明数据写入时就坏了 - 对比验证:同一数据在
mysql客户端查是好的、应用查是乱的 → 问题在应用连接层;两边都乱 → 看存储字节判断是否写入时已坏 - 关键原则:列内容的实际编码必须与列声明的字符集一致(官方手册明确);"
latin1列里存着utf8字节"是常见的"贴错标签"场景——字节没坏,只是声明错
- 看存储字节:
第二层:字符集链路与常见原因
- 四层链路:客户端实际编码(应用/终端)→ 连接三件套(
character_set_client/character_set_connection/character_set_results)→ 服务器(character_set_server只作建库默认值,不参与连接转换)→ 库/表/列字符集 - 常见原因:
- 连接字符集与实际数据编码不一致(最常见):终端/应用是
UTF-8,连接却是latin1或反之 - 表/列字符集错误:
latin1存中文、该用utf8mb4却用了utf8(存不下emoji/生僻字) - 写入时已乱码:数据进库那一刻就错了,之后改字符集也救不回来
- 纯显示问题:
SSH终端、Windows记事本、客户端工具显示编码不对 - 应用层未指定字符集:
JDBC URL、PHP、Python连接参数缺characterEncoding/charset
- 连接字符集与实际数据编码不一致(最常见):终端/应用是
- 四层链路:客户端实际编码(应用/终端)→ 连接三件套(
第三层:诊断命令
1 2 3 4SHOW VARIABLES LIKE 'character_set%'; -- 看连接三件套与服务器/数据库字符集 SHOW VARIABLES LIKE 'collation%'; SHOW CREATE TABLE t; -- 看表与列的字符集 SELECT HEX(name) FROM t LIMIT 5; -- 看存储字节,判断数据是否已坏第四层:解决方案(新数据——统一
utf8mb4)服务器层(my.cnf):
1 2 3 4 5 6[mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci # 8.0 默认 utf8mb4_0900_ai_ci,5.7 建议 unicode_ci [client] default-character-set = utf8mb4连接层:
SET NAMES utf8mb4(等价于同时设置client/connection/results三变量,仅会话级,每次连接都要执行或靠连接池initSQL/驱动参数)应用层:
JDBC:URL加characterEncoding=UTF-8(Connector/J 8.0.13+映射utf8mb4;旧写法characterEncoding=utf8映射的是utf8mb3,存emoji会失败;8.0.26+连8.0服务器默认即utf8mb4)PHP:charset=utf8mb4;Python:charset='utf8mb4'
建库建表显式指定(不依赖服务器默认值):
1 2CREATE DATABASE db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE TABLE t (id INT, name VARCHAR(100)) DEFAULT CHARACTER SET utf8mb4;
第五层:解决方案(存量数据——按字节实际编码处理)
场景
A:列声明utf8/latin1,字节确实是该字符集编码(如latin1列存latin1字节)→ 直接转码:ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4;按旧字符集解释并转码到新字符集场景
B:列声明latin1,但实际字节是utf8编码(贴错标签) → 直接CONVERT会二次转码更乱,分两步:先ALTER TABLE t MODIFY col BLOB(去掉字符集信息、字节原样保留),再MODIFY col VARCHAR(100) CHARACTER SET utf8mb4;也可用等价的一行写法:1 2UPDATE t SET col = CONVERT(CAST(col AS BINARY) USING utf8mb4); -- 字节原样取出,按 utf8mb4 解释 -- 之后别忘了 ALTER 修改列声明,否则下次写入再次错位前提:列内所有字节必须同一编码(混合编码无法正确转换,官方明确警告)
场景
C:- 整库迁移:
mysqldump导出 → 修改dump文件的字符集声明(SET NAMES/CHARSET)→ 导入新库,比逐表ALTER更稳
- 整库迁移:
迁移注意:
utf8mb3→utf8mb4时VARCHAR长度与索引前缀限制会收紧(如COMPACT行格式索引前缀191vs255),迁移前先检查列长度与索引
协助记忆
- 字符集就像一本密码本。文字转换成字节后,
MySQL需要用对应的密码本把字节解读回来。客户端用utf8mb4编码,MySQL却用latin1解读,就相当于拿错了密码本,于是出现乱码。所以解决乱码的关键就是:编码和解码使用同一套字符集规则。 - 口诀:先
HEX验货,后统一utf8mb4,存量按字节编码转码
- 字符集就像一本密码本。文字转换成字节后,
进阶思考
utf8和utf8mb4到底差在哪?utf8是utf8mb3的别名,只能存BMP基本区字符;emoji(4 字节)、部分生僻字需要utf8mb4(5.5.3 引入,最多 4 字节)。8.0 中utf8别名已弃用,新项目应显式写utf8mb4
为什么"查出来是 ? 或问号"和"查出来是锟斤拷"不一样?
- 问号通常是写入时编码无法映射(如
utf8存emoji被截断丢弃);“锟斤拷"是GBK字节被按UTF-8解释的典型乱码特征。两者修复路径不同:前者数据已损坏难恢复,后者改对解释方式即可
- 问号通常是写入时编码无法映射(如
改了
my.cnf字符集,为什么已有连接还是乱?- 字符集在连接建立时确定(三件套来自客户端握手请求),
my.cnf只影响新连接;已有连接需重连,且应用侧连接参数同样要改——服务器、连接、应用三层要一起动
- 字符集在连接建立时确定(三件套来自客户端握手请求),
8.0客户端连5.7服务器为什么反而乱码?8.0默认--default-character-set=utf8mb4连带默认collation utf8mb4_0900_ai_ci,而5.7不认识0900排序规则,会静默回退latin1——跨版本连接要显式指定collation(如utf8mb4_general_ci)
扩展信息
- 默认字符集:
8.0默认character_set_server=utf8mb4、默认排序utf8mb4_0900_ai_ci(Unicode 9.0无重音不区分大小写);5.7默认latin1/latin1_swedish_ci——— 升级到8.0后新库默认就带中文支持 utf8别名弃用:8.0中utf8作为utf8mb3别名已标记弃用(8.0.28起SHOW/Information Schema显示utf8mb3),未来可能改指utf8mb4,新代码显式写utf8mb4- 连接回退坑:
8.0客户端连5.7服务器时0900 collation不被识别会静默回退latin1(高频乱码来源,见进阶思考)
- 默认字符集:
🤔 什么是 MySQL 的表空间?
表空间(tablespace)是 InnoDB 存储引擎把数据组织到物理文件上的逻辑存储单元——表的数据和索引都存放在表空间中,向下对应具体的数据文件。InnoDB 的表空间体系分五类:系统表空间、独立表空间、通用表空间、undo 表空间、临时表空间;5.7 默认每张表独立一个 .ibd 文件,8.0 把数据字典迁出了 ibdata1(redo log 不属于表空间,别混淆)。
第一层:表空间是什么
- 逻辑 vs 物理:表空间是
InnoDB的逻辑存储容器,一个表空间对应一个或多个物理数据文件(如ibdata1、table.ibd) - 包含什么:表数据(聚簇索引)、二级索引、
undo日志、数据字典、change buffer等按类型分散在不同表空间中 - 注意区分:
redo log(ib_logfile*)是InnoDB的日志文件,不属于表空间
- 逻辑 vs 物理:表空间是
第二层:五大表空间(5.7 主线)
- 系统表空间(system tablespace) :默认文件
ibdata1(ibdata1:12M:autoextend,可配置多个文件)。5.7中承载数据字典、doublewrite buffer、change buffer和undo log;当innodb_file_per_table=OFF时,表数据也放这里 - 独立表空间(file-per-table) :每张表一个
.ibd文件(库名/表名.ibd),存该表的数据与索引;innodb_file_per_table自5.6.6(手册正文写"5.6 起”)默认开启——这是主流生产形态 - 通用表空间(general tablespace) :
5.7.6+引入,CREATE TABLESPACE创建,多张表共享一个.ibd,介于系统表空间与独立表空间之间,适合同类小表归并 undo表空间:存放回滚段(undo log)。5.7默认放系统表空间(innodb_undo_tablespaces默认0),可配置独立undo表空间,但该参数已废弃、只能在实例初始化时配置;系统表空间始终保留1个rollback segment(innodb_rollback_segments默认128)- 临时表空间(temporary tablespace) :
5.7引入的共享临时表空间ibtmp1(默认ibtmp1:12M:autoextend),存放磁盘临时表(非压缩)与内部临时表,随实例重启重建;压缩临时表(ROW_FORMAT=COMPRESSED)仍在独立表空间
- 系统表空间(system tablespace) :默认文件
第三层:关键参数
1 2 3 4 5 6[mysqld] innodb_data_file_path = ibdata1:12M:autoextend # 系统表空间文件定义 innodb_file_per_table = ON # 独立表空间(默认 ON) innodb_autoextend_increment = 64 # 系统表空间自动扩展增量(M;只作用于系统表空间,不影响 .ibd) innodb_undo_tablespaces = 0 # 5.7:独立 undo 数量(已废弃,初始化时配置) innodb_temp_data_file_path = ibtmp1:12M:autoextend # 5.7:临时表空间第四层:运维要点
ibdata1无法收缩:系统表空间不能直接删文件缩小,官方唯一方案是导出数据 → 新实例导入(或重建实例)。这是"ibdata1太大"的标准处理方式- 独立表空间的优缺点:优点是
DROP/TRUNCATE表直接删.ibd归还空间、避免系统表空间无限膨胀;缺点是每表独立fsync开销、小表碎片多、表多时文件描述符占用多 file_per_table=OFF时:所有表数据都进系统表空间,DROP表不归还空间(ibdata1只增不减)——除非有特殊理由,不建议关undo独立 ≠ibdata1收缩:5.7即使配置独立undo表空间,已写入ibdata1的历史undo空间也不会自动收回,收缩仍要走重建
协助记忆
- 表空间像图书馆的存放体系 ——— 独立表空间是一本书一个格子(
table.ibd),系统表空间ibdata1是总档案柜(数据字典、双写缓冲、change buffer全堆一起),通用表空间是"同类书共用一个柜子",undo表空间是"废纸回收箱",临时表空间是"临时借阅台"。档案柜(ibdata1)一旦堆满没法直接换小,只能整个图书馆重建搬书——这就是"ibdata1无法收缩"的本质 - 口诀 :表数据在
.ibd,元数据在ibdata1,redo是日志不是表空间
- 表空间像图书馆的存放体系 ——— 独立表空间是一本书一个格子(
进阶思考
为什么要从"所有表都在
ibdata1“改成"一表一个.ibd"?- 早期(5.6.6 前)所有表挤在
ibdata1,DROP表空间不归还、单文件巨大难维护;file-per-table让每个表独立文件,删表即回收、备份/迁移按表粒度,代价是每表独立fsync与碎片
- 早期(5.6.6 前)所有表挤在
ibdata1 已经 200G 了怎么处理?
- 不能在线收缩。标准做法:
mysqldump导出(或物理备份)→ 在新实例(或重建数据目录)导入;8.0中数据字典已迁出ibdata1,此问题已大幅缓解
- 不能在线收缩。标准做法:
8.0 里 ibdata1 变小了吗?
- 是的。8.0 数据字典迁入
mysql.ibd,ibdata1主要只剩change buffer(8.0.20 起doublewrite也迁出到独立.dblwr文件),ibdata1不再是"总档案柜”
- 是的。8.0 数据字典迁入
undo 表空间能删吗?
- 不能
DROP;只能truncate收缩(且需至少 2 个undo表空间才能轮换truncate)。8.0默认 2 个 undo 表空间(undo_001/undo_002),不再允许放回系统表空间
- 不能
🤔 什么情况下会发生死锁?
死锁是多个事务互相持有对方需要的锁、谁也不释放,形成循环等待。
InnoDB有死锁检测器,检测到就自动回滚"修改行数最少"的事务,应用收到ERROR 1213(SQLSTATE 40001)后重试即可。高发场景集中在加锁顺序不一致、间隙锁与范围更新、唯一键冲突、外键约束;避免的核心是"固定加锁顺序 + 短事务 + 缩小锁范围"。第一层:死锁是什么(先懂机制)
- 定义:每个事务都持有别人需要的锁、又在等待别人持有的锁,形成循环等待(
circular wait)——官方定义:"each transaction holds a lock that is needed by another one" - 四个必要条件(通用并发理论):互斥、持有并等待、不可剥夺、循环等待——四个同时满足才会死锁
InnoDB的应对:不是等死,而是主动检测——事务等待锁时检查等待图有没有环,有环就回滚其中一个(挑选插入/更新/删除行数最少的小事务,官方是"tries to pick small transactions",且可能回滚多个),释放它的锁让其他事务继续- 注意:死锁报错是
ERROR 1213(40001);锁等待超时是另一回事——等待超过innodb_lock_wait_timeout(默认50秒)报ERROR 1205(HY000),一个是主动回滚、一个是被动放弃
- 定义:每个事务都持有别人需要的锁、又在等待别人持有的锁,形成循环等待(
第二层:
InnoDB高发死锁场景- 加锁顺序不一致(最常见) :事务
A先锁表1再锁表2,事务B先锁表2再锁表1,互相卡住;多行更新顺序相反同理——官方明确"transactions lock rows in multiple tables... in the opposite order" - 二级索引与回表锁交叉:
UPDATE/DELETE经二级索引定位时,先锁二级索引记录、再锁对应聚簇索引记录;两个事务走不同索引路径,锁的获取顺序交叉形成环 - 范围查询的间隙锁(gap lock) :
RR隔离级别下范围条件加的是next-key lock(记录锁 + 前间隙锁);相邻范围的事务要插入新行,需先拿插入意向锁,与对方持有的gap锁冲突,形成死锁——这是RR下最常见的死锁来源 - 唯一键冲突检查:插入重复唯一键值时,
InnoDB会在唯一性检查阶段对已存在的重复记录加共享锁(S锁) ;两个事务同时插入同一个唯一键,互相持S等对方释放,死锁 - 外键约束:插入/更新子表记录时,要对父表对应行加共享锁做外键检查;与父表行上的排他锁(
DELETE/UPDATE持有)交叉即死锁 - 批量/范围
UPDATE边界不同:两个事务更新同一范围,因执行时序各自拿到部分锁,边界错位互相等对方释放 - 单行也能死锁:单行
INSERT并非原子——它要在多个索引记录(含gap、唯一键检查)上自动加锁,两个事务在相邻位置/同一唯一键上交错等待同样会死锁
- 加锁顺序不一致(最常见) :事务
第三层:死锁检测与相关参数
- 检测器开关:
innodb_deadlock_detect(5.7.15引入,5.7/8.0均有)默认ON;极高峰并发场景可关闭以减少检测开销,但关闭后死锁只能靠innodb_lock_wait_timeout(默认 50 秒)兜底,等待期间锁会堆积,谨慎操作 - 检测边界:官方说明等待列表超过
200个事务或锁检查超过1,000,000次时即视为死锁回滚 - 检测开销:大量线程等待同一把锁时,死锁检测本身也有开销——这是部分高并发场景关检测的原因
- 检测器开关:
第四层:
- 如何排查死锁:
SHOW ENGINE INNODB STATUS;– 看LATEST DETECTED DEADLOCK段:两个事务的 SQL、持有锁、等待锁 - 全量记录:
SET GLOBAL innodb_print_all_deadlocks = ON;(5.6.2+)把每一次死锁都写入 MySQL 错误日志(默认只记最近一次) - 锁监控:
5.7用performance_schema.data_locks / data_lock_waits(5.7 起已存在);应用侧捕获 ERROR 1213 并按业务重试
- 如何排查死锁:
第五层:如何避免死锁
- 固定加锁顺序:所有事务按相同顺序访问多表/多行(如统一按主键升序),从根上消除循环等待——官方第一建议
- 事务短小:尽快提交释放锁,缩小"持有并等待"的时间窗口
- 缩小锁范围:等值条件代替范围条件(减少 gap 锁)、合理索引让锁少而准(官方明示"
well-chosen indexes... set fewer locks")、避免大范围批量更新 - 降低隔离级别:官方建议"
try using a lower isolation level such as READ COMMITTED"——RC 下禁用gap locking(next-key 退化为记录锁),能降低死锁概率;注意官方同时指出死锁的根源是写操作、与隔离级别无必然关系,RC 只是减少触发面 - 应用层重试:官方明确always be prepared to re-issue a transaction if it fails due to deadlock——1213 是预期内错误,重试机制必须有
协助记忆
- 死锁像单行桥上两车对向会车 ——
A车占着桥头等B让路,B车占着桥尾等A让路,谁都不退。交警(死锁检测器)来了,直接拖走一辆车(回滚小事务),其余车继续走;但如果交警没来(检测关闭),两车只能干等到天荒地老(50 秒锁超时)。避免方法就是约定"都靠右走"(统一加锁顺序)和"别在桥上逗留"(短事务) - 口诀 :顺序一致、事务短小、少用范围锁,1213 来了就重试
- 死锁像单行桥上两车对向会车 ——
进阶思考
死锁和锁等待超时是一回事吗?
- 不是。死锁是检测器发现循环等待主动回滚,秒级报
ERROR 1213;锁等待超时是单方等待超过innodb_lock_wait_timeout(默认 50 秒)被动放弃,报ERROR 1205。前者是系统纠错,后者是系统放弃
- 不是。死锁是检测器发现循环等待主动回滚,秒级报
为什么"单行插入"也会死锁?
- 单行
INSERT不是原子的,要插入意向锁 + 唯一键检查 + 多个索引记录(含 gap)上自动加锁;两个事务在相邻 gap 或同一唯一键上交错等待,同样形成环——这就是官方even a single-row insert can deadlock的原因
- 单行
关闭死锁检测有什么风险?
- 死锁不再被主动发现,只能靠 50 秒锁超时兜底;这 50 秒内锁一直被占、等待链堆积,可能拖垮高并发业务。除非压测证明检测开销是瓶颈,否则不建议关
RC 隔离级别能彻底避免死锁吗?
- 不能。RC 只是禁用了 gap lock、减少死锁触发面,但加锁顺序不一致、唯一键冲突、外键检查等场景照样死锁——官方明确死锁的产生与隔离级别无必然关系,根源是写操作的锁交互
扩展信息
- 锁监控表更替:8.0 中 information_schema.innodb_locks / innodb_lock_waits 已移除,performance_schema.data_locks / data_lock_waits 成为唯一途径(这两张表 5.7 已存在,8.0 起完全取代 I_S 表)
- 8.0 autoinc 相关:8.0 的 AUTO-INC 锁机制(默认 interleaved 模式)减少插入自增锁的互相阻塞,但仍需注意插入意向锁与 gap 的交互
🤔 MySQL 主流高可用方案有哪些?
高可用 = 复制(数据同步)+ 切换(故障转移)两层能力,评价看 RPO(最多丢多少数据)与 RTO(多久恢复)。主流方案分四类:经典 MHA(5.7 存量时代主力)、官方 MGR / InnoDB Cluster(8.x 主线)、Orchestrator(复杂拓扑)、云 RDS(托管免运维);选型先定版本、再定可接受的 RPO/RTO。
第一层:高可用的核心要素
- 复制层:把主库数据同步到备库——异步(默认,主库崩溃可能丢已提交事务)、半同步(至少一个备库确认才提交,缩小丢失窗口)、组复制(共识协议强一致)
- 切换层:主库故障时把流量切到新主——人工脚本、
MHA/Orchestrator自动切换、官方InnoDB Cluster自动选主、云RDS托管切换 - 评价指标:
RPO(Recovery Point Objective,数据丢失上限)、RTO(Recovery Time Objective,恢复时长);典型:MHA秒级RTO、异步复制有秒级~分钟级RPO
第二层:复制基础层(先有数据同步)
- 异步复制(默认) :主库提交即返回,不等待备库确认——性能好,但主库崩溃时已提交但未传到备库的事务会丢(官方原文:if the source crashes, transactions that it has committed might not have been transmitted to any replica)
- 半同步复制(semi-sync) :官方插件(
5.5引入,需安装启用,默认关闭),主库等至少一个备库确认收到relay log才提交,把RPO压到接近0;注意超时会自动回退异步,仍留丢失窗口 MGR(MySQL Group Replication,5.7.17+ 官方插件) :基于Paxos共识协议,支持单主(自动选主)与多主模式,组内多数派存活即高可用,内置防脑裂机制;强制要求GTID
第三层:切换/管理方案
MHA(Master High Availability):经典第三方方案(yoshinorim开发)。监控主库,故障时自动选新主、从各从库补齐relay log后切换,秒级RTO;原版已停更(最后推送约2020年) ,对MySQL 8.0支持有限(8.0默认认证插件与MHA的Perl驱动兼容性问题是常见坑),社区有多个8.0 fork但无公认活跃维护版,需谨慎评估——属于"5.7存量现状"而非推荐新用Orchestrator:GitHub开源(openark/orchestrator),管理MySQL复制拓扑(级联、多主、拓扑可视化),自动检测故障并重挂从库、切换;注意两点:Raft只用于orchestrator自身多节点HA(leader选举),不参与MySQL数据面;原仓库已归档(archived) 、最后release 3.2.6(2022 年),无公认活跃后继,生产使用需评估- 双主 + VIP(keepalived) :经典双主互备 + 虚拟
IP漂移。简单直接,但有脑裂风险(两侧同时写),必须配fencing机制;生产建议"只写一侧 + 复制用于切换",不要真双写
第四层:官方一体化方案(InnoDB Cluster)
- 组成:
MGR(组复制)+MySQL Shell的AdminAPI(dba.createCluster一键建集群)+MySQL Router(读写自动路由,主故障自动把应用流量切到新主) - 版本要求:必须
MySQL 8.0+,5.7只能用裸Group Replication,组不了InnoDB Cluster;官方要求至少3实例(多数派) - 适用:新项目/8.0 环境官方推荐路线,自动化程度最高;配合
Clone插件(8.0.17+)可快速加节点
- 组成:
第五层:中间件与云托管
ProxySQL等中间件:做读写分离、后端健康检查与故障剔除(应用只连中间件,后端切换对应用透明);注意ProxySQL不做复制切换决策——主从切换仍由MHA/Orchestrator/人工完成后再改路由- 云
RDS:AWS Multi-AZ、阿里云高可用版等自带主备切换与多可用区能力,RPO≈0(内部半同步)但RTO通常分钟级,且复制拓扑/账号权限受云厂商限制——云上首选,省运维
第六层:选型建议
- 5.7 存量生产:
MHA+ 半同步是历史主流组合(半同步保证备库有数据、MHA切换时补齐relay log,RPO接近0);但要意识到5.7已EOL、MHA已停更,属存量维护策略 - 新项目 /
8.x:官方路线InnoDB Cluster/MGR(单主)+MySQL Router;追求官方统一、少自研脚本 - 复杂复制拓扑:
Orchestrator曾是首选,鉴于已归档,需评估社区fork或自研兜底 - 云上:直接用云
RDS高可用,别自己搭
- 5.7 存量生产:
协助记忆
- 复制像飞机的备份引擎——异步是"副引擎有没有同步看运气"(可能丢数据),半同步是"副引擎确认点火才起飞"(少丢数据);切换像换飞行员——MHA 是熟练老副驾(老牌但已退役),InnoDB Cluster 是自动驾驶系统(官方、自动接管),Orchestrator 是塔台调度(管多架飞机的拓扑),云 RDS 是包机服务(托管,省心)。选型就是问自己:飞机多老(版本)、能接受丢多少(RPO)、多久能复飞(RTO)
- 主库是发货仓,备库是分拨仓/备份仓,数据就是"货";
- 异步复制:货打包完先发出去再说——主仓出事时,还有货在路上/没打包(丢件 = RPO)
- 半同步:收件仓必须签收确认(ACK) 才算发货完成,货没签收不敢接下一单
- MGR:多个分拨仓用多数派确认统一调度,防止各仓各说各话(防脑裂)
- MHA:老调度员——经验丰富但已退休,只能翻旧记录,新系统(8.0)不太会用
- Orchestrator:全网调度平台,管所有分拨仓的转运拓扑(级联、多仓)
- InnoDB Cluster:智慧物流系统——自动调度 + 自动路由,货自动走可用仓
- 云 RDS:直接把物流外包给菜鸟驿站/顺丰托管,省心
- 口诀:复制保数据、切换保可用,5.7 靠 MHA、8.x 走 Cluster
进阶思考
半同步 +
MHA为什么能接近零丢失?- 半同步保证"已提交事务已写入至少一个备库的
relay log";MHA切换时从各备库收集并补齐relay log再提升新主——数据在主库和备库两侧都有,主库宕机也不丢。但半同步超时会自动回退异步,回退期间仍有丢失窗口
- 半同步保证"已提交事务已写入至少一个备库的
MGR和InnoDB Cluster是一回事吗?- 不是。
MGR是复制引擎(共识协议,5.7.17起有);InnoDB Cluster是"MGR+ 管理工具 + 路由"的完整高可用方案(8.0起,5.7组不了Cluster)。可以说InnoDB Cluster是"开箱即用的MGR"
- 不是。
双主 +
VIP和MGR都能防脑裂吗?- 双主 +
VIP没有内置防脑裂,需要外部fencing(如STONITH)兜底;MGR内置自动防脑裂机制(多数派存活才服务,quorum丢失整体停服保护数据一致性)——这是官方方案更稳的关键差异
- 双主 +
扩展信息
ClusterSet(8.0.21+):InnoDB Cluster的跨地域容灾方案,多个Cluster组成ClusterSet,提供地域级故障转移Clone插件(8.0.17+) :物理克隆加节点,替代传统备份恢复的provisioning方式,InnoDB Cluster/MGR加节点更快MGR演进:8.0.32引入group write consensus、8.4引入Single Consensus Leader,持续优化共识路径- 生命周期提醒:
5.7已EOL、8.0 Premier Support已结束(Extended阶段),新项目考虑8.4 LTS