
说实话MySQL的常用命令网上已经被人整理过无数遍但每次接手新项目、带新人我都要重新讲一遍连接、建表、查数、备份这些最基础的东西。原因很简单很多人不是不知道命令而是不熟悉版本差异——5.7时代的写法拿到8.0/8.4上要么报警告要么直接报错。这篇以MySQL 8.0/8.4系列为准整理了一套日常开发、运维真正用得上的常用命令从登录连接到库表管理从增删改查到索引优化从权限备份到事务排障都是我这些年实际敲过、踩过坑的命令。新手可以照着从头过一遍老手建议重点看版本差异相关的部分后面有几处确实是2026年环境下才会遇到的坑。1. 连接与日常体检先把“能不能连上”这件事搞明白1.1 命令行登录的三种姿势登录是所有人接触MySQL的第一关。最常见的写法mysql -u root -p回车之后输入密码即可。这里有个非常容易被忽略的细节-p和密码之间不能有空格写成-p 123456会被解析成“输入空密码、并把 123456 当作数据库名”。虽然正规操作不推荐把密码直接写在命令行里但脚本里偶尔会这么干务必注意。如果实例端口不是默认的 3306或者需要远程连接用-h指定主机、-P指定端口注意 P 是大写mysql -h 127.0.0.1 -P 3306 -u root -p新手最容易困惑的一点是-h后面如果写localhost在部分系统上 MySQL 客户端会优先走 Unix Socket 而不是 TCP导致-P端口参数根本不生效。确认当前连接是怎么建立的可以执行SELECT hostname, port, CURRENT_USER();这个语句一次性能把主机名、端口、当前账号看清楚排查“我明明连的是 A 库怎么数据像 B 库”这类问题时非常好用。另外还有一个版本相关的坑MySQL 8.0 起默认认证插件从mysql_native_password换成了caching_sha2_password旧版客户端5.7.x 及更早在连接时会直接报错Authentication plugin caching_sha2_password cannot be loaded。这个问题的核心不是密码输错而是老客户端不认识新插件。解决方案是优先升级客户端和驱动而不是把服务端改回老插件——mysql_native_password在 MySQL 8.4 里已经进入弃用阶段继续依赖旧插件迟早要出事。1.2 连接后的体检命令版本、字符集与 SSL 排查登录之后先别急着跑业务 SQL花十秒钟做个体检。我每次连新环境的固定动作是SELECT VERSION(); SELECT NOW(); SHOW VARIABLES LIKE character_set_server; SHOW VARIABLES LIKE collation_server; SHOW VARIABLES LIKE port; SELECT hostname;重点是字符集。character_set_server是实例级默认字符集你在一个utf8mb4实例上建表和一个latin1实例上建表默认行为完全不同。之前遇到过线上导出的备份文件在本地恢复后中文全部变成问号查了半天发现是本地实例的character_set_server还停留在老配置导出导入都没加--default-character-set问题就这么一点一点攒出来的。再提一个最近总被问到的 SSL 问题。新版 MySQL 客户端默认会尝试加密连接如果实例没开 SSL 或者证书配置有问题会报ERROR 2026 (HY000): SSL connection error。排查分两步先看服务端是否支持 SSLSHOW VARIABLES LIKE have_ssl;返回YES说明支持再看客户端参数。如果确认是内网环境、业务又不要求传输加密可以临时用--ssl-modeDISABLED连接跳过证书验证环节mysql -u root -p --ssl-modeDISABLED这只是排查手段生产环境数据链路的加密需求还是要靠正确配置 SSL 证书来解决别图省事一关了之。连接层面最后补一句应用连 MySQL 报Too many connections本质也是连接管理的事。命令行里可以这样看SHOW VARIABLES LIKE max_connections; SHOW STATUS LIKE Threads_connected;如果Threads_connected长期贴着max_connections多半是应用侧连接池配得过大或者有连接泄漏。命令行能看到现象根因要结合应用日志定位。2. 库和表管理建库、改表、删表的完整套路2.1 库级别的操作一个 MySQL 实例里可以建多个库库之间的隔离靠权限控制。建库建议写全CREATE DATABASE IF NOT EXISTS shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;IF NOT EXISTS是防呆设计脚本重复执行不会报错。DEFAULT CHARACTER SET和COLLATE要刻意指定因为只依赖实例默认值的话换环境就可能不一致。utf8mb4和utf8mb4_unicode_ci是当前主流选择utf8mb4支持 emoji 和四字节字符utf8mb4_unicode_ci在排序规则上比老式utf8_general_ci更规范。MySQL 8.0 之后默认就是utf8mb4但建库时写清楚永远是最稳的习惯。查看库、切换库SHOW DATABASES; USE shop; SELECT DATABASE();USE之后不确定当前处于哪个库时SELECT DATABASE()直接确认。改库级字符集一般只在迁移或出现乱码时用ALTER DATABASE shop CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;DROP DATABASE这个命令要非常慎重它会连库带表一起删除没有回收站。我见过有人在测试库路径下敲错了库名把备份库删了。团队内部建议任何 DROP 操作之前先SHOW DATABASES确认一遍名字有条件的话先在从库上演练一次。2.2 表结构的查看与维护建表命令看起来长核心结构其实就几部分CREATE TABLE IF NOT EXISTS user ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键, name VARCHAR(64) NOT NULL COMMENT 姓名, email VARCHAR(128) DEFAULT NULL COMMENT 邮箱, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_email (email) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;几个容易忽略的点INT UNSIGNED可以避免未来id溢出NOT NULL和DEFAULT组合能减少业务脏数据created_at用DATETIME加DEFAULT CURRENT_TIMESTAMP而不是交给应用层手工写入唯一索引用uk_前缀命名方便识别。ENGINEInnoDB也要写虽然 8.0 之后默认就是 InnoDB但明确指定能让读 DDL 的人一眼明白意图。查看表结构的方式有三种SHOW TABLES; DESC user; SHOW CREATE TABLE user;DESC适合快速看字段SHOW CREATE TABLE能输出完整建表语句是排查字段字符集、默认值、索引问题的利器。之前帮人排查中文乱码最后就是在SHOW CREATE TABLE的输出里发现某一列还是老字符集库是 utf8mb4列却不是问题一下定位了。日常线上改表最多的几个动作ALTER TABLE user ADD COLUMN age TINYINT UNSIGNED DEFAULT 0 COMMENT 年龄; ALTER TABLE user MODIFY COLUMN age INT UNSIGNED COMMENT 年龄; ALTER TABLE user CHANGE COLUMN age user_age INT UNSIGNED COMMENT 年龄; ALTER TABLE user DROP COLUMN user_age; ALTER TABLE user RENAME TO customer;ADD是加列MODIFY是改列的类型和属性CHANGE是改列名加改属性DROP是删列RENAME是改表名。注意MODIFY和CHANGE在大表上可能触发全表重建执行前先评估数据量和预计锁时间最好放在低峰期。热搜词里有人搜“mysql 设置默认值为 0”其实就是在MODIFY时加上DEFAULT 0ALTER TABLE t MODIFY COLUMN status TINYINT NOT NULL DEFAULT 0;2.3 DROP、TRUNCATE、DELETE 三兄弟的区别经常有新人把清空数据的几种方式混在一起。下面这张表是我培训时一直用的操作类型是否可回滚是否重置自增行为速度DELETEDML事务内可回滚不重置逐行删除可加 WHERE慢TRUNCATEDDL隐式提交不可回滚重置清空全表快DROPDDL隐式提交不可回滚表没了删除表结构和数据最快实际经验DELETE不加 WHERE 等于全表删但 DML 还能走事务回滚TRUNCATE清空后自增 ID 从 1 重新开始如果业务上对 ID 有连续性要求虽然不建议依赖 ID 连续这个影响很大DROP直接删表结构删完只能从备份恢复。我的建议任何删除操作之前先想清楚有没有备份。生产环境删除类语句最好走工单审批执行前先用 SELECT 把影响的数据捞出来核对一遍确认无误后再换成 DELETE / TRUNCATE / DROP 执行。这个流程多花两分钟能避免绝大多数“回滚靠备份”的惨案。3. 数据操作增删改查与排序分页3.1 插入数据的四种常见写法最基础的 INSERT 是单条插入INSERT INTO user (name, email) VALUES (张三, zhangsantest.com);批量插入是性能利器应用层拼 SQL 时也建议聚合后一次提交INSERT INTO user (name, email) VALUES (李四, lisitest.com), (王五, wangwutest.com), (赵六, zhaoliutest.com);一条语句插入多行比循环执行单条 INSERT 快一个数量级因为减少了客户端和服务端的往返次数。但批量条数不是越大越好超过几千上万行会拉长单次事务的锁持有时间反而容易造成主从延迟和锁等待。业务里最常见的“存在就更新、不存在就插入”场景用ON DUPLICATE KEY UPDATEINSERT INTO user (id, name, email) VALUES (100, 张三, newtest.com) ON DUPLICATE KEY UPDATE email VALUES(email);这段在 MySQL 8.0.20 之后会收到VALUES()函数弃用警告新写法是用行别名INSERT INTO user (id, name, email) VALUES (100, 张三, newtest.com) AS new ON DUPLICATE KEY UPDATE email new.email;INSERT IGNORE的作用是遇到唯一键冲突就静默跳过适合批量灌数时只想插入新记录的场景。但要注意它同时也会把其他类型错误吞掉比如字段超长、非空约束失败导致日志里查不到失败原因生产环境用之前要想清楚。3.2 更新与删除WHERE 永远不能省更新、删除的常规写法UPDATE user SET email newtest.com WHERE id 1; UPDATE user SET email newtest.com WHERE id IN (1, 2, 3); DELETE FROM user WHERE id 5;这两类命令最大的风险就是 WHERE 写错或者漏写。MySQL 默认autocommit1一条不带 WHERE 的 UPDATE 会瞬间把整表数据改掉连个确认都不会弹。我自己的习惯是执行 UPDATE / DELETE 之前先复制一份 SQL把关键词换成 SELECT 跑一遍看结果集SELECT id, email FROM user WHERE id 1;确认影响的行和预期一致再回去执行 UPDATE / DELETE。这个习惯看起来多了一步实际能省掉大量“误操作后找备份”的时间。另一个实用点如果确实要全表更新可以考虑分批提交。每次 LIMIT 5000用WHERE id 上次最大 id的游标形式推进避免一次性锁大量行导致主从延迟飙升。3.3 查询与排序ORDER BY 里藏着不少细节SELECT 是最高频的操作过滤条件写法很固定SELECT id, name, email FROM user WHERE status 1; SELECT * FROM user WHERE age BETWEEN 18 AND 30; SELECT * FROM user WHERE name LIKE 张%; SELECT * FROM user WHERE email IS NULL; SELECT * FROM user WHERE id IN (SELECT user_id FROM orders WHERE amount 100);几个日常注释尽量不SELECT *只取需要的列减少网络传输和 MySQL 层无谓开销BETWEEN是闭区间包含两端LIKE的%放在哪里决定了能否用索引前缀匹配张%有索引优化空间后缀匹配%张基本只能全表扫描。排序最常见的写法SELECT * FROM user ORDER BY created_at DESC; SELECT * FROM user ORDER BY dept_id ASC, created_at DESC;第一条按时间倒序第二条先按dept_id升序再按created_at降序。多列排序的优先级是从左到右只有前一列值相同时才会比较后一列。几个容易踩坑的点。第一是 NULL 排序MySQL 在升序时 NULL 排最前面降序时 NULL 排在最后。如果希望 NULL 固定沉底SELECT * FROM user ORDER BY created_at IS NULL, created_at DESC;第二是中文排序。默认字符集排序规则下中文按 Unicode 编码排序看起来不是按拼音。想按拼音排小数据量可以临时转成 GBK 比较SELECT name FROM user ORDER BY CONVERT(name USING gbk) ASC;这个方法只建议小数据量展示场景大数据量不建议在 ORDER BY 里用函数索引会直接失效。第三是排序与索引的关系ORDER BY 字段如果在索引里且 WHERE 条件也命中索引MySQL 可以免去 filesortEXPLAIN 结果里出现Using filesort时要结合业务评估要不要加复合索引。3.4 分页 LIMITOFFSET 越大越慢分页的核心语法是 LIMIT 加两个参数SELECT * FROM user ORDER BY id LIMIT 20, 10;表示跳过 20 行取 10 行也就是第 3 页数据。另一种等价写法SELECT * FROM user ORDER BY id LIMIT 10 OFFSET 20;我习惯用LIMIT offset, count这种老写法但读别人 SQL 时两种都要认识。LIMIT 最大的坑是深分页。offset1000000时MySQL 仍然要先扫描并排序 1000010 行再丢弃前 1000000 条数据量大了接口会明显变慢。优化方案是“游标分页”不跳 offset记住上一页最后一条的 id下一页直接查SELECT * FROM user WHERE id 100000 ORDER BY id LIMIT 10;首页依然可以用正常 LIMIT从第二页开始用id 上一页最大 id。这种写法在数据量大且 id 分布均匀时性能稳定。如果业务必须支持任意页码跳转那就要考虑在排序字段上建复合索引或者把数据按时间维度拆到分区表里。4. 查询进阶聚合、分组、多表连接与子查询4.1 聚合函数与 GROUP BY报表和统计离不开聚合函数SELECT COUNT(*) FROM user WHERE status 1; SELECT dept_id, AVG(salary), MAX(salary), MIN(salary), SUM(salary) FROM employee WHERE status active GROUP BY dept_id;COUNT(*)和COUNT(字段)的区别要记牢COUNT(*)统计所有行COUNT(salary)只统计salary不为 NULL 的行。如果业务希望把 NULL 当作 0 计入可以配合COALESCE不过这种需求很少见大多数时候大家关心的只是“这到底是不是匹配行数”。GROUP BY 还有个高频报错点。MySQL 8.0 在only_full_group_by模式下SELECT 里的非聚合列必须出现在 GROUP BY 里否则直接报错SELECT dept_id, name, AVG(salary) FROM employee GROUP BY dept_id; -- 错误name不在分组键里结果集里 dept_id 相同可能有多个不同 name取谁都不合理。解决办法是明确业务语义如果想看每个人的薪资就不要 GROUP BY dept_id如果只要部门维度SELECT 就只写分组键和聚合值。4.2 WHERE 和 HAVING 的分工WHERE 在分组前过滤HAVING 在分组后过滤两者完全不是一回事。SELECT dept_id, COUNT(*) AS cnt FROM employee WHERE status active GROUP BY dept_id HAVING cnt 5;这条 SQL 的执行顺序大致是FROM - WHERE - GROUP BY - HAVING - SELECT - ORDER BY - LIMIT。先 WHERE 把离职、试用期的员工滤掉再按部门分组统计最后 HAVING 只保留人数超过 5 的部门。理解这个顺序比死记命令重要得多。一个常见的优化点能在 WHERE 里过滤的条件就不要在 HAVING 里写。HAVING 要等分组和聚合执行完才能过滤白白浪费计算量。4.3 JOIN三种连接的直观理解多表业务几乎离不开 JOIN。最典型的是订单表和用户表SELECT o.order_no, u.name, o.amount FROM orders o INNER JOIN user u ON o.user_id u.id WHERE o.status paid;INNER JOIN只保留两边都匹配的行。LEFT JOIN保留左表全部行右表没有匹配就补 NULLSELECT u.name, o.order_no, o.amount FROM user u LEFT JOIN orders o ON u.id o.user_id WHERE o.order_no IS NULL;这条 SQL 能查出“从未下过单的用户”是 LEFT JOIN 的经典用法。生活化类比INNER JOIN 是双方都有联系方式才拉群LEFT JOIN 是左边的人全部进群右边能匹配就匹配匹配不上就空着。写 JOIN 时要注意ON后面是连接条件WHERE后面是过滤条件两别混。把过滤条件放到 WHERE 有时会改变 LEFT JOIN 的语义。比如上面查“未下单用户”的例子如果把o.status paid写进 ON 而不是 WHERE结果就完全不对了。这也是面试里高频考察的点。4.4 子查询与 EXISTS子查询就是嵌套查询。最简单的标量子查询SELECT * FROM employee WHERE dept_id (SELECT id FROM dept WHERE name 技术部);标量子查询返回一行一列适合等值判断。多行匹配用 INSELECT * FROM employee WHERE dept_id IN (SELECT id FROM dept WHERE name IN (技术部, 产品部));EXISTS 写法适合“判断存在性”而不关心子查询具体值的场景SELECT * FROM user u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id u.id AND o.amount 1000);EXISTS 是关联子查询外层表每行都要执行一次存在性判断但子查询一旦匹配到第一条就会停止扫描配合好用的索引性能反而不差。IN 和 EXISTS 到底谁快不能拍脑袋和表大小、索引、数据分布都有关系实践时建议用 EXPLAIN 对比。团队里新人经常问“哪种写法更高级”我的回答是先跑 EXPLAIN 再说执行计划比感觉靠谱。5. 索引、执行计划与慢日志性能问题的三板斧5.1 索引的创建、查看与删除索引是 MySQL 性能的命根子。基础命令CREATE INDEX idx_name ON user(name); CREATE UNIQUE INDEX uk_email ON user(email); SHOW INDEX FROM user; DROP INDEX idx_name ON user;CREATE INDEX直接加字段名即可UNIQUE INDEX表示唯一索引。复合索引更常用CREATE INDEX idx_dept_salary ON employee(dept_id, salary);这个索引能同时加速按dept_id过滤、按dept_id salary组合过滤的查询但要注意最左前缀原则查询条件里没出现dept_id时idx_dept_salary帮不上忙。设计复合索引时把等值过滤字段放前面、范围过滤字段放后面是基本套路。5.2 EXPLAIN 怎么看EXPLAIN 是排查 SQL 性能的核心工具。用法很简单EXPLAIN SELECT u.name, o.order_no FROM user u LEFT JOIN orders o ON u.id o.user_id WHERE u.status 1;重点看这几列字段含义需要警惕的值type访问类型ALL、indexkey实际使用的索引NULL 表示没用到rows预估扫描行数越大越危险Extra附加信息Using filesort、Using temporarytype 从好到坏大致是system const eq_ref ref range index ALL。看到 ALL 基本说明全表扫描如果表有几十万行而且这个 SQL 要频繁执行就该考虑加索引了。rows 是优化器估算的扫描行数不是精确值但量级很能说明问题。Extra 里出现Using filesort说明排序没走索引要考虑调整索引或改写 SQL。补充一个经验EXPLAIN 的结果只是优化器基于成本和统计信息的估算统计信息过期时可能给出误导性结论所以定期执行ANALYZE TABLE维护统计信息也是个好习惯。5.3 慢查询日志定位慢 SQL 最直接的手段线上找慢 SQL最直接的办法是慢查询日志。可以动态开启SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1; SET GLOBAL slow_query_log_file /var/log/mysql/slow.log;long_query_time单位是秒设为 1 表示超过 1 秒的 SQL 都会被记录。注意SET GLOBAL只对本次运行有效重启 MySQL 后失效。生产环境要持久化写进 my.cnf 或 my.ini[mysqld] slow_query_log 1 long_query_time 1 slow_query_log_file /var/log/mysql/slow.logLinux 下查看慢日志最常用的是 grep 和 lessgrep -i Slow /var/log/mysql/slow.log配合mysqldumpslow工具可以自动汇总排序mysqldumpslow -s t /var/log/mysql/slow.log-s t表示按查询时间排序。拿到慢 SQL 之后用 EXPLAIN 分析一般无非两类问题缺索引或者 SQL 写法导致索引失效。5.4 索引失效的经典场景这块值得单列因为“加了索引但还是慢”是排查最多的一个现象。常见有四类第一对索引列用了函数SELECT * FROM user WHERE YEAR(created_at) 2025;对created_at用了 YEAR 函数索引就失效了。正确写法是范围查询SELECT * FROM user WHERE created_at 2025-01-01 AND created_at 2026-01-01;第二隐式类型转换。mobile列是 VARCHAR但条件写成数字SELECT * FROM user WHERE mobile 13800000000;MySQL 会把列转成数字再比较索引失效。建议查询参数和列类型保持一致或者在 SQL 里显式加引号。第三LIKE 以%开头SELECT * FROM user WHERE name LIKE %张%;只有前缀匹配张%才能用索引。第四OR 条件里有非索引列SELECT * FROM user WHERE name 张三 OR status 1;只要 OR 的一侧没有索引整个查询就可能退回全表扫描。改写思路是拆成两个查询用 UNION ALL 合并或者给status也建索引。这些场景我在排查线上问题时几乎每天都能见到规范里写“禁止对索引列使用函数、禁止隐式类型转换”背后就是这些真实案例。6. 用户权限与数据安全授权要细、备份要勤6.1 用户管理与远程访问权限管理是 DBA 的日常也是开发上线前必做的一步。用户操作四件套CREATE USER app_userlocalhost IDENTIFIED BY StrongPass123; CREATE USER app_user% IDENTIFIED BY StrongPass123; ALTER USER app_userlocalhost IDENTIFIED BY NewPass456; DROP USER app_userlocalhost;账号由“用户名 主机”两部分组成app_userlocalhost只允许本机连app_user%允许任意 IPapp_user192.168.1.%允许指定网段。安全上建议最小化主机范围能限制网段就不要用%。密码不要用纯数字和弱口令生产环境建议启用validate_password组件强制密码强度。开发环境常见需求是把某个测试库开放给指定 IPCREATE USER test_user192.168.1.50 IDENTIFIED BY TestPass123; GRANT SELECT, UPDATE ON test_db.* TO test_user192.168.1.50;先建用户再授权两步分开写清晰明了。这里有个版本差异5.7 里有人写GRANT ... IDENTIFIED BY一条语句建用户并授权8.0 已经移除了这种写法必须分成CREATE USER和GRANT两条。6.2 GRANT、REVOKE 与权限查看GRANT 的粒度可以从全局到表级GRANT ALL PRIVILEGES ON *.* TO adminlocalhost; GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO app_userlocalhost; GRANT SELECT ON shop.t_user TO readonly_userlocalhost;权限收得越细出问题的影响面越小。给应用的账号通常只授业务表需要的 DML 权限不要给 DDL不要给GRANT OPTION。查看权限和回收权限SHOW GRANTS FOR app_userlocalhost; REVOKE DELETE ON shop.* FROM app_userlocalhost;一个容易被误解的点通过GRANT、REVOKE、CREATE USER这些官方语句修改权限后不需要执行FLUSH PRIVILEGES。只有直接 UPDATEmysql.user表之类的底层表操作才需要FLUSH PRIVILEGES让权限表重新加载。网上很多教程不管三七二十一都让刷新一下其实很多场景是多余的。另外说一个安全底线把root远程授权给所有 IP 并且用弱密码这是最大的漏洞之一。之前审计过一套系统就是因为 root 远程开放加弱口令被扫库拖走了数据。给应用连库一定要单独建账号权限按需分配这是底线中的底线。6.3 mysqldump 备份的常用姿势备份是“平时用不上出事救命”的动作。最常用的逻辑备份工具是 mysqldump。备份单个库mysqldump -u root -p --single-transaction --default-character-setutf8mb4 shop shop_backup.sql解释几个关键参数--single-transaction对 InnoDB 表启用一致快照备份过程中不阻塞业务写入--default-character-setutf8mb4防止导出文件里中文乱码重定向把 SQL 语句写入文件。如果库里有存储过程和触发器基础导出不会包含这些对象要显式加参数mysqldump -u root -p --single-transaction --routines --triggers shop shop_full.sql--routines导出存储过程和函数--triggers导出触发器这对应了不少人搜“mysql 存储过程”的需求。备份整个实例所有库用--all-databasesmysqldump -u root -p --single-transaction --all-databases all_backup.sql给一个我常用的 Linux 定时备份脚本片段配合 crontab 每天执行#!/bin/bash BACKUP_DIR/data/backup/mysql DATE$(date %Y%m%d%H%M) mysqldump -u backup_user -pBackupPass \ --single-transaction --routines --triggers \ --default-character-setutf8mb4 \ shop ${BACKUP_DIR}/shop_${DATE}.sql find ${BACKUP_DIR} -name shop_*.sql -mtime 30 -exec rm -f {} \;核心思路是每天全量保留 30 天配合 binlog 可以做时间点恢复。备份账号别用 root单独建一个有SELECT、SHOW VIEW、RELOAD权限的账号更安全。6.4 数据恢复的两种方式备份文件本质上是 SQL 语句文本恢复就是执行这些语句。命令行方式mysql -u root -p shop shop_backup.sql先确保目标库存在不存在就先创建CREATE DATABASE IF NOT EXISTS shop DEFAULT CHARACTER SET utf8mb4;如果已经登录进 mysql 客户端用 source 命令mysql source /data/backup/mysql/shop_202601011200.sql;source和的区别不大source是客户端内置命令是 shell 重定向。实际使用中source更方便调试能看到每条语句的执行状态。恢复大文件时建议加上超时参数避免默认交互超时断开mysql --connect-timeout600 -u root -p shop big_backup.sql最后给一个经验如果误删了一张表又恰好没有当天备份不要慌先确认是不是开了 binlog。确认 binlog 开启后可以用mysqlbinlog解析出对应时间段的 DELETE / UPDATE 语句反转出恢复 SQL。这个操作依赖binlog_formatROW所以生产环境建议设置 row 格式不仅恢复方便主从一致性也更好。具体命令细节可以单独写一篇但“有 binlog 就不至于从零恢复”这个意识要建立起来。7. 事务、锁与运维排障并发场景和日常巡检7.1 事务控制与隔离级别事务是保证数据一致性的基石。手动事务的标准流程START TRANSACTION; UPDATE account SET balance balance - 100 WHERE id 1; UPDATE account SET balance balance 100 WHERE id 2; COMMIT;如果第二步执行出错可以回滚ROLLBACK;半路后悔但还没提交时也可以用保存点做部分回滚SAVEPOINT sp1; UPDATE ...; ROLLBACK TO sp1;查询当前隔离级别SELECT transaction_isolation;注意 8.0 里tx_isolation已经废弃正确写法是transaction_isolation。默认一般是REPEATABLE-READ如果想降低间隙锁影响可以改成READ-COMMITTEDSET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;这只是会话级不影响其他连接改全局需要SET GLOBAL。事务必须显式 COMMIT否则会一直持有锁。我排查线上锁等待时最常见的元凶就是应用代码里开了事务忘了提交。7.2 进程、锁与并发问题排查并发问题要先看当前连接在干什么SHOW PROCESSLIST; SHOW FULL PROCESSLIST;FULL能看到完整 SQL 语句不截断。输出里有Id、User、Host、db、Command、Time、State、Info几列。重点关注 Time 很长且 State 是Waiting for table metadata lock或Waiting for lock的会话这些往往是性能问题的来源。确认某条会话是阻塞源头可以直接终止KILL 12345;KILL要小心线上直接杀掉正常业务连接会造成客户端报错但卡死的慢查询或锁等待连接该杀就得杀。稳妥做法是先查它正在执行的 SQL确认不是关键业务再执行 KILL。死锁发生后MySQL 默认只保留最近一条死锁信息查看命令SHOW ENGINE INNODB STATUS\G重点看LATEST DETECTED DEADLOCK这一段里面会列出两个事务各自持有的锁、等待的锁以及触发死锁的 SQL。我遇到过几次死锁最后都是在输出里发现是两个事务申请锁的顺序相反导致的调整应用层加锁顺序就解决了。7.3 状态变量、日志与 Windows 服务问题日常巡检不需要复杂的监控系统几条状态命令足够SHOW STATUS LIKE Threads_connected; SHOW STATUS LIKE Threads_running; SHOW STATUS LIKE Uptime; SHOW VARIABLES LIKE max_connections; SHOW STATUS LIKE Max_used_connections;Threads_running持续很高说明并发负载大Max_used_connections和max_connections接近说明连接数资源快耗尽要排查应用连接池配置或调大上限。排障离不开日志。错误日志位置SHOW VARIABLES LIKE log_error;错误日志记录启动失败、连接异常、主从复制错误等是 MySQL 排障的第一站。二进制日志用于复制和恢复SHOW BINARY LOGS; SHOW BINLOG EVENTS IN mysql-bin.000001 LIMIT 20;在 MySQL 8.4 里旧版SHOW MASTER STATUS和SHOW SLAVE STATUS已经被移除改用SHOW BINARY LOG STATUS; SHOW REPLICA STATUS;这是个典型的版本差异用旧教程复制粘贴会直接报语法错误这也是为什么我说要看准 2026 年的最新行为。binlog 文件虽然是二进制格式但可以用mysqlbinlog把内容解析成可读 SQLmysqlbinlog --start-datetime2026-01-01 00:00:00 /var/log/mysql/bin.000001最后应对一下 Windows 上“net start mysql 服务无法启动”这类问题。命令行启动服务报错时第一步永远是去看错误日志Windows 安装版的日志默认在数据目录下文件以.err结尾路径一般配置在 my.ini 里。最常见的几种原因my.ini 里的basedir/datadir路径不对、3306 端口被占用、data 目录没有初始化。新装 MySQL 8.0 忘记执行初始化命令就会出现服务起不来mysqld --initialize-insecure这个命令会生成一个无密码的 root 账号仅限本机初始化后再用net start mysql启动然后立刻ALTER USER设置密码。如果是 Docker 环境起不来先看容器日志docker logs 容器名容器内的错误日志路径和 Windows 略有不同大概率是挂载权限或初始化脚本的问题查容器日志比猜快得多。排障的顺序永远是“日志优先”无论哪个平台这个习惯都不会变。