资讯详情

资讯详情

MySQL常用指令实战:从连接、索引到备份恢复的完整指南

MySQL 的常用指令说难不难说简单也容易踩坑。我这些年给团队做过不少数据库层面的支持和排查发现很多人其实卡在几个点上命令背得下来但不知道什么场景下该用哪一条在测试环境敢执行一到生产环境就手抖平时备份恢复全靠图形工具一旦终端环境里只剩一个 mysql 命令行客户端就不知道从哪里下手。所以这篇不打算单纯罗列命令而是按真实的使用场景把日常开发、运维里真正用得到的指令串一遍从连接数据库、建表、增删改查到索引优化、权限管理和备份恢复每一段都会解释为什么这样写、有哪些常见坑。适合刚接触 MySQL 的开发者也适合想把手头操作重新梳理清楚的后端工程师或运维同学。1. 动手之前环境准备与连库基础约定1.1 连接 MySQL 的几种方式不管你是本地开发还是登录线上跳板机第一步基本都是连上 MySQL 实例。最基础的命令是mysql -u root -p-u指定用户名-p表示需要输入密码。这里有个细节我每次都要强调-p后面不要直接跟密码。比如mysql -u root -p123456这种写法虽然能连上但密码会明文出现在终端历史记录里如果机器被日志采集或者有人翻看 history密码就等于裸奔了。指定主机和端口时用-h和-Pmysql -h 10.0.0.8 -P 3306 -u root -p如果数据库服务跑在默认端口 3306-P 3306可以省略。但跨环境连接时我习惯把-h、-P都显式写出来一方面减少想当然另一方面是方便之后改造成脚本。还有两类参数值得养成习惯mysql --default-character-setutf8mb4 -u root -p连接时显式指定utf8mb4字符集可以避免终端里中文乱码也能减少因为连接字符集和库表字符集不一致导致的乱码问题。如果日常操作中经常要执行耗时较长的查询还可以建议配合--safe-updates这个参数启动。它属于 MySQL 客户端的一种保护模式会拦截没有 WHERE 条件的大范围 UPDATE 或 DELETE 操作对新手非常友好不过只适用于交互式命令行脚本里一般不常用。连接成功之后退出用exit;或者quit;也可以直接\q效果一样。这三个都记住就行因为有时候在不同环境下肌肉记忆会短路。另外一个容易被忽略的便捷手段是登录配置mysql_config_editor set --login-pathlocal --hostlocalhost --userroot --password执行后会提示输入密码之后连接只需要mysql --login-pathlocal这种方式的好处是密码没有明文写在命令行里而是存在当前用户家目录下的加密配置文件里。不同环境可以用不同 login-path 区分比如dev、prod比在 command line 里裸写密码安全得多。1.2 查看版本、当前用户与库表清单连上数据库后第一件事通常是确认自己连的是哪个环境、哪个版本。我经常看到有人连接后直接执行业务 SQL结果发现连错库浪费一晚上排查时间。所以先来一组“体检语句”SELECT VERSION(); SELECT CURRENT_USER(), DATABASE(); SHOW DATABASES;SELECT VERSION()返回 MySQL 版本号。不同版本语法细节有差异比如utf8mb4_0900_ai_ci这个排序规则是 MySQL 8.0 才有的5.7 会报错。CURRENT_USER()返回当前登录用户DATABASE()返回当前选中的库如果没有选库结果会是 NULL。SHOW DATABASES列出所有可见的库这个权限取决于当前用户。确认环境没问题后用USE选中目标库USE app_db;之后可以查看库下所有表SHOW TABLES;想快速看某张表长什么样有两种方式DESC users; SHOW CREATE TABLE users;DESC输出比较简洁只有字段列表SHOW CREATE TABLE会把完整的建表语句打印出来包括索引、约束、字符集设置适合你要复制表结构或者判断两张表是否一致时使用。我个人习惯把这几条命令记成“环境自查四连”VERSION()、CURRENT_USER()、DATABASE()、SHOW TABLES。每次连上一个陌生环境先把这四条依次跑一遍用时不到十秒却能避免大量低级的误操作。2. 数据库与表结构操作DDL2.1 创建数据库、字符集与排序规则的选择创建数据库看起来是件小事但字符集和排序规则选错后患无穷。一个比较稳妥的建库语句是CREATE DATABASE IF NOT EXISTS app_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;IF NOT EXISTS是一种保护重复执行不会报错适合写进初始化脚本。utf8mb4是目前最主流的选择它能完整存储中文、英文、数字和 emoji 字符。utf8mb4_0900_ai_ci是 MySQL 8.0 默认的排序规则0900对应 Unicode 9.0 标准ai表示不区分重音ci表示不区分大小写适合大多数业务场景。如果你用的是 MySQL 5.7那么建库最好写成COLLATE utf8mb4_general_ci因为 5.7 不认识utf8mb4_0900_ai_ci。这个问题在运维脚本里特别容易出现以为是环境差异其实是版本差异。查看当前库的字符集和排序规则SHOW CREATE DATABASE app_db;如果建完之后发现字符集选错了可以修改ALTER DATABASE app_db CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;不过要注意修改数据库默认字符集只会影响之后新建的表已经存在的表需要单独ALTER TABLE才能改这是一个经常被忽略的点。删除数据库的指令很简单DROP DATABASE app_db;但这个操作我没少见过事故。生产环境执行 DROP 前一定要确认当前连接选中的库并且最好先做一次备份。有些团队甚至会在生产账号上把 DROP 权限收掉只保留给 DBA 专用账号我觉得这是非常合理的做法。2.2 建表语句的设计与字段类型选择建表是 DDL 里最核心的部分。一个基础的用户表示例CREATE TABLE IF NOT EXISTS users ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键ID, username VARCHAR(64) NOT NULL COMMENT 用户名, email VARCHAR(128) DEFAULT NULL COMMENT 邮箱, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态1启用0禁用, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username), KEY idx_email (email) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT用户表;关于字段类型我踩过一些坑简单说几个原则id用BIGINT UNSIGNED不要用INT。现在业务数据增长很快INT最大才 21 亿多看着不少但一旦某个表成了热点表翻车也就是一两年的事。AUTO_INCREMENT是自增配合主键使用。username用VARCHAR(64)而不是TEXT。用户名通常有长度限制用VARCHAR可以建索引TEXT虽然能存更多内容但作为索引会有额外限制而且排序和比较性能也差一些。VARCHAR的长度一般按业务上限再加一点余量不要随手写255因为VARCHAR(255)在部分索引长度计算场景下会和VARCHAR(64)有差异。status这类枚举值用TINYINT而不是VARCHAR。用数字表示状态占空间小查询也快。如果你担心可读性可以用COMMENT写清楚每个值含义。created_at和updated_at用DATETIME或TIMESTAMP是永恒的争论。MySQL 8.0 中DATETIME的默认行为已经比 5.7 好用配合DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP可以做到插入时自动写入创建时间、更新时自动刷新修改时间不用在应用层手动维护。这在大多数业务里都是最简单省事的方案。索引方面主键索引必须要有唯一索引用于业务上需要保证唯一的字段比如用户名、手机号、订单号。KEY idx_email (email)给 email 加普通索引是为了后续按邮箱查询时不用全表扫描。但索引不是越多越好这个后面专门讲。建表时显式指定ENGINEInnoDB原因之一是 InnoDB 支持事务和行级锁这对绝大多数业务是刚需。另一个原因是 MySQL 8.0 之后默认就是 InnoDB但老版本可能默认是 MyISAM显式声明可以避免环境差异。字符集和排序规则在建表时也要显式写出不然就会继承库级设置将来某张表出现特殊情况时排查起来更麻烦。2.3 修改表结构的常用语句与注意事项业务迭代过程中给表加字段是特别常见的事。MySQL 里修改表结构都走ALTER TABLEALTER TABLE users ADD COLUMN phone VARCHAR(20) DEFAULT NULL COMMENT 手机号 AFTER email;在email字段后面新增一个phone字段。如果你不关心字段顺序可以省略AFTER email新字段会被加到表末尾。AFTER这个语法有意思但生产环境大表加字段时字段顺序其实不影响功能新字段默认放最后也完全没问题没必要强行指定顺序而多读一遍表结构。删除字段ALTER TABLE users DROP COLUMN phone;这个操作在数据量大的表上一定要谨慎因为字段删除后数据就没了而且大表 DDL 会带来锁表风险。修改字段类型ALTER TABLE users MODIFY COLUMN username VARCHAR(128) NOT NULL COMMENT 用户名;MODIFY会修改字段类型、是否为空、注释等属性但它不改字段名。如果连字段名也要改就用CHANGEALTER TABLE users CHANGE COLUMN username login_name VARCHAR(64) NOT NULL COMMENT 登录名;CHANGE后面第一个是旧字段名第二个是新字段名然后是类型定义。这里有个容易犯错的地方CHANGE需要把完整的字段定义重新写一遍如果漏了NOT NULL或COMMENT原来的约束可能会被覆盖改成默认值所以使用时要格外小心。修改表名的语句是ALTER TABLE users RENAME TO members;或者写RENAME TABLE users TO members;效果相同。需要注意的是表名改了之后依赖这个表名的存储过程、视图、定时任务、应用代码可能全部要同步调整不是一条 SQL 就能收工的。索引操作同样走ALTER TABLE也可以独立使用CREATE INDEXCREATE INDEX idx_phone ON users(phone); ALTER TABLE users DROP INDEX idx_phone;生产环境大表的 DDL 操作我一直坚持的原则是能放到低峰期就放到低峰期能拆分就拆分。MySQL 8.0 虽然支持在线 DDL但大表执行期间仍可能产生主从延迟极端情况下会影响线上读写。如果你管理的是核心业务库建议先用SHOW PROCESSLIST看一下当前连接情况再挑一个相对空闲的时间窗口操作。3. 数据的增删改查DML3.1 INSERT 语句的常见写法和批量插入技巧插入数据最基础的写法INSERT INTO users (username, email, status) VALUES (zhangsan, zhangsanexample.com, 1);我建议永远显式列出字段名不要用INSERT INTO users VALUES (...)这种不写字段的写法。原因有两个第一表结构一旦调整比如新增了字段不写字段名的插入语句就可能报错或数据错位第二不写字段名时阅读代码的人很难判断每个值对应哪个列维护成本高。一次插入多行数据时可以写成INSERT INTO users (username, email, status) VALUES (lisi, lisiexample.com, 1), (wangwu, wangwuexample.com, 1), (zhaoliu, zhaoiliuexample.com, 0);多行插入比逐行插入性能好很多因为减少了客户端和数据库之间的网络往返。如果有几千行要插入可以分批执行每批 500 到 1000 行既不会让单条 SQL 太长也能获得不错的性能。处理重复数据时有两条语句很实用。一条是INSERT IGNORE INTO users (username, email) VALUES (zhangsan, zhangsanexample.com);INSERT IGNORE遇到唯一键冲突时会静默跳过不报错。另一条是INSERT INTO users (username, email, status) VALUES (zhangsan, zhangsanexample.com, 0) ON DUPLICATE KEY UPDATE status VALUES(status);这句的意思是如果唯一键冲突就执行后面的 UPDATE。VALUES(status)在 MySQL 8.0.20 之后有了新写法官方更推荐使用别名方式但VALUES()这种写法在现存代码里依然大量存在看到时不要懵。需要补充的是这类“存在则更新、不存在则插入”的场景在高并发下容易产生死锁特别是多个事务同时操作同一批 key 时。如果业务里出现类似报错可以考虑在事务里先用SELECT ... FOR UPDATE锁定行再决定是 UPDATE 还是 INSERT或者干脆把这类操作收敛到消息队列串行处理。3.2 SELECT 查询的核心写法与常用条件查询是使用频率最高的操作。基础的SELECT格式SELECT id, username, email, status, created_at FROM users WHERE status 1;这里有一个所有开发都知道但总有人偷懒的点不要用SELECT *。在研发环境可以因为数据量小但在生产环境SELECT *会把所有列都查出来尤其是某些表已经加了content、remark这种大字段时不仅增加网络传输还会让查询变慢。如果你只需要id和username就只写这两个字段。另一个原因和覆盖索引有关后文讲索引时会再提到。常用的过滤条件包括SELECT id, username FROM users WHERE email LIKE %example.com; SELECT id, username FROM users WHERE status IN (0, 1); SELECT id, username FROM users WHERE created_at BETWEEN 2024-01-01 AND 2024-12-31; SELECT id, username FROM users WHERE phone IS NULL;LIKE的写法要注意以%开头的模糊查询无法走普通索引比如LIKE %example.com所以数据量大时会有明显的性能问题。IN列表别放太多值否则会变成一次扫大范围的查询。BETWEEN是包含边界的两端都是闭区间很多人误以为是开区间。分页也是高频需求SELECT id, username FROM users ORDER BY id DESC LIMIT 20 OFFSET 40;LIMIT 20表示取 20 条OFFSET 40表示跳过前 40 条。这个写法在页数比较小的时候没问题但一旦页码很大比如OFFSET 100000MySQL 会先把前 100000 条都查出来再丢弃性能会迅速恶化。对于深分页场景更推荐用“上次查询的最后一条 id”来翻页SELECT id, username FROM users WHERE id 100500 ORDER BY id DESC LIMIT 20;这样能直接利用主键索引定位而不是反复扫描前面的数据。CASE WHEN在查询里也很常用SELECT id, username, CASE WHEN status 1 THEN 启用 ELSE 禁用 END AS status_text FROM users;CASE WHEN适合在 SQL 里做简单的状态转换避免在应用层再循环判断。但不要在里面塞太复杂的逻辑否则 SQL 难读也难维护。3.3 UPDATE 与 DELETE 的安全姿势更新数据时最怕漏写WHERE条件。下面这条错误的 SQL 很多人手滑写过UPDATE users SET status 1;没有WHERE它会更新全表所有行的status如果这张表有几十万行线上业务基本就出事故了。所以我的习惯是先写WHERE再回过来写SET甚至先用SELECT把要影响的行查出来看一眼SELECT id, username, status FROM users WHERE id 10086; UPDATE users SET status 0 WHERE id 10086;这个习惯看起来低级但真的能救命。尤其是生产环境多一次确认少一次事故。更新时如果需要关联其他表MySQL 允许这样写UPDATE users u JOIN orders o ON o.user_id u.id SET u.status 0 WHERE o.created_at 2023-01-01 AND u.status 1;这个写法会把所有下单时间早于 2023 年的用户状态改成 0。好处是一次完成关联更新坏处是容易误伤因为连接条件稍微写错影响范围就不可控。我的建议是这种多表 UPDATE 尽量先改成 SELECT 验证结果集再执行 UPDATE。DELETE 的使用场景要区分两种情况。按条件删除DELETE FROM users WHERE id 10086;全表清空有两种方式DELETE FROM users; TRUNCATE TABLE users;TRUNCATE是 DDL 操作它会直接重建表速度很快但不能带WHERE也不能回滚。DELETE是 DML 操作逐行删除可以通过事务回滚但数据量大时会很慢还会产生大量 binlog。如果只是清空测试环境的表用TRUNCATE如果是生产环境需要清理数据必须走DELETE加事务并且分段执行。DELETE 配合子查询在某些版本中不能直接操作同表一个典型的坑是DELETE FROM users WHERE id IN (SELECT id FROM users WHERE status 0);在 MySQL 中可能报错You cant specify target table for update in FROM clause。解决办法是包一层DELETE FROM users WHERE id IN ( SELECT id FROM (SELECT id FROM users WHERE status 0) AS tmp );这算是老生常谈的坑了遇到时别慌套一层临时表就能解决。4. 进阶查询排序、分组与多表连接4.1 排序 ORDER BY 与 NULL 值的处理查询结果排序最基础的是SELECT id, username, status, created_at FROM users ORDER BY created_at DESC;DESC表示从大到小ASC表示从小到大。多字段排序比如先按状态排再按创建时间降序SELECT id, username, status, created_at FROM users ORDER BY status DESC, created_at DESC;排序顺序很重要写法是先满足第一个字段再在第一个字段相同的情况下按第二个字段排。如果你发现排序结果和预期不一样先检查字段顺序是不是反了。MySQL 里NULL的排序行为经常让人迷惑。默认情况下升序时NULL排在前面降序时NULL排在后面。如果你想把NULL固定排在最前面或最后面可以这样SELECT id, username, phone FROM users ORDER BY phone IS NULL, phone ASC;phone IS NULL这个表达式在 phone 为 NULL 时结果为 1否则为 0。升序时 1 在后所以 NULL 会排到最后。如果想让 NULL 排第一就改成ORDER BY phone IS NULL DESC, phone ASC。这个技巧虽然简单但能省不少事。还有一个和排序相关的问题是中文排序。默认情况下utf8mb4_0900_ai_ci排序规则会按照 Unicode 编码顺序排中文看不出来拼音顺序。如果业务需要按拼音排序一种常见写法是SELECT id, username FROM users ORDER BY CONVERT(username USING GBK);不过这个写法会让索引失效数据量大时不建议在线上直接执行更适合在离线分析环境里用。4.2 聚合函数 GROUP BY 与 HAVING 的用法需要统计数量、求和、平均值时聚合函数是首选。常见的有COUNT、SUM、AVG、MAX、MIN。比如统计每日新增用户数SELECT DATE(created_at) AS day, COUNT(*) AS cnt FROM users GROUP BY DATE(created_at) ORDER BY day DESC LIMIT 7;这里COUNT(*)会统计每一行的数量COUNT(1)效果相同。如果你只是想统计某个非空字段的数量可以用COUNT(column)它会忽略 NULL 值。这个差异在实际统计中很容易导致数据对不上比如统计手机号数量时如果用COUNT(*)会把没填手机号的用户也算进去而COUNT(phone)不会。GROUP BY之后取出的是分组列和聚合结果其他非聚合列不能随便出现在 SELECT 列表。MySQL 5.7 之后默认开启only_full_group_by模式如果你写了SELECT id, username, COUNT(*) FROM users GROUP BY status;会被直接报错因为id、username没有出现在GROUP BY中也不是聚合函数。如果确实想取某个分组内的一条记录可以配合MIN、MAX或者子查询来做而不是硬编码一个无意义的字段。过滤分组结果不能用WHERE要用HAVING。比如查用户数大于 100 的状态SELECT status, COUNT(*) AS cnt FROM users GROUP BY status HAVING cnt 100;这里的关键区别是WHERE是在分组之前过滤行HAVING是在分组之后过滤组。能用WHERE过滤掉的尽量不要用HAVING因为HAVING的执行效率相对更低而且会掩盖分组的语义。GROUP_CONCAT也很常用它能把分组内某个字段的值拼成字符串SELECT status, GROUP_CONCAT(username ORDER BY id SEPARATOR ,) AS user_list FROM users GROUP BY status;这个函数默认有长度限制默认是 1024 字符如果拼接结果超长会被截断。需要调整时可以执行SET SESSION group_concat_max_len 10240;4.3 JOIN 多表连接与连接条件的位置业务数据几乎不会只在一张表里多表连接是躲不开的。最常见的是INNER JOINSELECT u.id, u.username, o.order_no FROM users u INNER JOIN orders o ON o.user_id u.id WHERE u.status 1;INNER JOIN只返回两边都匹配的行比如查有订单的用户。LEFT JOIN则是以左表为主左表的行一定会出现右表中没有匹配的行则补 NULLSELECT u.id, u.username, o.order_no FROM users u LEFT JOIN orders o ON o.user_id u.id;这个查询会把所有用户都列出来即使没有订单order_no也会是 NULL。JOIN 写法里有一个经典坑过滤条件放在 ON 和 WHERE 里效果不同。比如想查状态正常的用户及其订单如果写成SELECT u.id, u.username, o.order_no FROM users u LEFT JOIN orders o ON o.user_id u.id AND u.status 1;那么LEFT JOIN左边的 users 表并不会被过滤连接条件里对左表的过滤不会缩减左表行数结果还是包含所有用户只是不满足条件的用户对应的订单是 NULL。所以对左表的过滤条件要放在WHERE中SELECT u.id, u.username, o.order_no FROM users u LEFT JOIN orders o ON o.user_id u.id WHERE u.status 1;对右表被驱动表的过滤条件放在ON和WHERE里也有区别放在WHERE中会把不匹配的行过滤成LEFT JOIN失效表现类似INNER JOIN放在ON中则保留左表行。这个细节要自己动手试一下才会真正理解文的文字解释可能绕但搞懂之后看执行计划就非常清楚了。自连接也是一种 JOIN比如一张员工表每个员工有个manager_id指向自己上级的员工 idSELECT e.username AS employee_name, m.username AS manager_name FROM employees e LEFT JOIN employees m ON m.id e.manager_id;这种写法在组织架构、菜单层级、分类树等场景中很常见。自连接并不难难的是理解同一张表在连接中扮演了两个角色。5. 索引、执行计划与性能优化5.1 索引的创建、查看与删除索引是提升查询性能的关键但也是最容易被乱用的东西。创建索引的语句CREATE INDEX idx_email ON users(email);也可以在建表后用ALTER TABLE添加ALTER TABLE users ADD INDEX idx_email (email);查看某张表上的索引SHOW INDEX FROM users;删除索引DROP INDEX idx_email ON users;索引的选择不是越多越好因为每次 INSERT、UPDATE、DELETE 时MySQL 都要同步维护索引索引太多会拖慢写入性能。一个常见原则是高频查询的过滤条件、连接条件和排序字段才适合建索引。联合索引是性能优化里最需要理解的。假设有一条高频查询SELECT id, username FROM users WHERE status 1 AND created_at 2024-01-01;那么在(status, created_at)上建联合索引比分别建两个单列索引效果更好CREATE INDEX idx_status_created_at ON users(status, created_at);联合索引遵循“最左前缀原则”。也就是说查询条件里必须包含联合索引的最左边字段索引才可能被用到。比如上面这个联合索引如果只按created_at查索引是用不上的如果只按status查则可以用上。这里有一个覆盖索引的概念。如果查询的字段都包含在索引里MySQL 可以直接从索引返回结果不需要回表查数据行效率会高很多。比如上面查询只查id和username而(status, created_at)联合索引其实不包含username所以严格说需要回表。如果查询和过滤字段组成一个(status, created_at, id, username)的索引那的确可能实现覆盖扫描但索引字段太多维护成本也高。实际项目中不必为了覆盖而过度设计先把最核心的查询链路优化好。5.2 用 EXPLAIN 读懂执行计划SQL 写的对不对、快不快不能靠猜得看执行计划。MySQL 里通过EXPLAIN查看EXPLAIN SELECT id, username FROM users WHERE email testexample.com;结果会返回一张表包含id、select_type、table、type、possible_keys、key、rows、Extra等列。其中比较重要的几个type表示访问类型从好到差大致是system、const、eq_ref、ref、range、index、ALL。ALL是全表扫描基本意味着这条查询会从头到尾扫一遍表数据量大时性能堪忧。key表示实际使用的索引如果为 NULL说明没用到索引。rows是预估扫描的行数数字越大代价越高。Extra里如果出现Using filesort说明排序没走索引需要在内存或磁盘中额外排序如果出现Using temporary说明查询中用了临时表通常是GROUP BY或DISTINCT比较复杂造成的。比如一条原本走了全表扫描的查询EXPLAIN SELECT id, username FROM users WHERE status 1 AND created_at 2024-01-01;如果在(status, created_at)上有联合索引type可能会变成rangekey显示实际使用的索引名rows明显变少这就是优化生效了。用EXPLAIN最常见的场景是排查为什么这条 SQL 在测试环境秒回、生产环境却慢得离谱。绝大多数情况是测试环境数据量只有几千行全表扫描也不慢但生产环境上亿行后ALL就会变成灾难。所以看执行计划时不要只看有没有索引还要看type和rows。5.3 慢查询日志的定位方式数据库慢查询不能用肉眼一条条猜。MySQL 提供了慢查询日志先确认当前的慢查询配置SHOW VARIABLES LIKE slow_query_log; SHOW VARIABLES LIKE long_query_time;如果没开启可以临时开启SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1;这里long_query_time单位是秒设置成 1表示执行时间超过 1 秒的 SQL 会被记录。注意这个变量是全局的但新连接生效想在当前会话立即生效也可以再执行一次SET SESSION long_query_time 1。慢查询日志会写到一个文件里比如/var/lib/mysql/host-slow.log。定位慢查询文件的变量SHOW VARIABLES LIKE slow_query_log_file;然后可以用mysqldumpslow工具做汇总mysqldumpslow -t 10 /var/lib/mysql/host-slow.log这样可以按照执行次数或耗时把最严重的 Top 10 慢 SQL 列出来。拿到慢 SQL 之后再结合EXPLAIN分析为什么慢这才是完整的优化流程。不要一上来就给所有字段加索引那样只会让写入变慢。6. 用户权限与备份恢复的常用指令6.1 用户创建、授权与回收权限线上业务代码连接数据库时最忌讳的是直接使用root账号。root权限太大一旦连接串泄露整个库都暴露了。正确的做法是创建专用账号只授予业务需要的权限。创建用户CREATE USER app_user192.168.% IDENTIFIED BY StrongPassword123!;这里app_user192.168.%表示只允许来自192.168.x.x网段的主机登录%是通配符表示任意主机。账密里如果包含特殊字符尤其是单引号和分号要注意转义避免写坏整条语句。授权的基本写法GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO app_user192.168.%;这句话把app_db库下所有表的增删改查权限给了应用账号。如果应用需要执行 DDL再加ALTER、CREATE、DROP等但生产环境我通常不建议给应用账号 DDL 权限表结构变更应该走专门的管理流程。查看账号权限SHOW GRANTS FOR app_user192.168.%;收回权限REVOKE DELETE ON app_db.* FROM app_user192.168.%;修改密码ALTER USER app_user192.168.% IDENTIFIED BY NewPassword456!;删除用户DROP USER app_user192.168.%;在 MySQL 8.0 中GRANT语句创建用户的方式已经废弃必须先CREATE USER再GRANT。有些老教程会写GRANT ALL PRIVILEGES ON *.* TO user% IDENTIFIED BY password在 8.0 会报语法错误。如果遇到这个问题先检查版本。关于FLUSH PRIVILEGES很多文章说授权后要执行才能生效。实际上通过GRANT、CREATE USER、ALTER USER这类语句修改权限后MySQL 会自动重新加载权限表一般不需要手动执行FLUSH PRIVILEGES。只有在直接操作了mysql.user表等系统表之后才需要用FLUSH PRIVILEGES强制生效。这个知识点没什么技术难度但能回答不少同事的经典疑问。6.2 备份与恢复的实操命令备份是最后一根救命稻草平时用不上出故障时才知道有没有用。最常用的备份工具是mysqldump。备份单个数据库mysqldump -u root -p --single-transaction --master-data2 --default-character-setutf8mb4 app_db backup_20250101.sql这里解释几个参数--single-transaction对 InnoDB 表做一致性快照备份备份过程中不锁表特别适合在线备份。MyISAM 表不支持它会自动退化为锁表。--master-data2会在备份文件里记录 binlog 位置方便做基于时间点的恢复也适合用来搭建从库时参考。--default-character-setutf8mb4保证备份时用正确的字符集导出避免中文乱码。如果不想要表结构只备份数据可以加mysqldump -u root -p --no-create-info --single-transaction app_db backup_data_only.sql如果只想要表结构不想要数据mysqldump -u root -p --no-data app_db backup_schema_only.sql恢复备份最常用的是直接通过 mysql 客户端导入mysql -u root -p app_db backup_20250101.sql也可以进入 mysql 交互环境后执行source /path/to/backup_20250101.sql;恢复之前要注意目标库要提前创建好因为mysqldump默认不会帮你创建数据库本身。另外导入大文件时建议先确认连接字符集与备份文件一致否则中文数据可能变成乱码。备份和恢复这套流程我见过最多的坑是脚本写了但从没实际恢复过。等到真正故障发生时才发现备份文件损坏、磁盘不够、权限不对、字符集错了。所以靠谱的做法是每隔一段时间找一台闲置的测试实例做一次完整的恢复演练。备份不是把文件导出来就完事了能恢复出来的备份才有意义。恢复演练的同时也能顺带验证备份脚本中的参数是否还适用于当前版本。7. 写在最后的几句实操体会MySQL 指令本身翻来覆去就是连接、建表、增删改查、索引、权限、备份这些东西单看每一条都不难。但我这几年在线上环境摸爬滚打下来最深的感觉是指令之间的配合和操作时机比指令本身更重要。比如前面反复提到的生产环境执行UPDATE、DELETE前先SELECT看影响范围执行大表ALTER TABLE前关注锁和主从延迟给业务账号按最小权限分配而不是图省事直接给ALL PRIVILEGES备份脚本至少每月演练一次恢复。这些都不是什么炫技操作却能在关键时刻避免事故。个人建议新手朋友在本地搭建一套 MySQL 环境不需要多复杂一个 Docker 容器就够了。把本文里出现的指令挨个敲一遍尤其是EXPLAIN和SHOW CREATE TABLE这种平时容易被忽略的查询多看看输出结果。指令敲多了自然就变成肌肉记忆以后再遇到线上问题至少知道从哪里下手排查。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →