一、结论先给
MySQL 命令分两类:查询类(随便跑)和变更类(跑之前先想好回滚)。真正出事的从来不是不会写 SQL,而是没带 WHERE 的 UPDATE、没先备份的 DROP、没限流的大表 ALTER。
| 操作 | 危险等级 | 前置动作 |
|---|---|---|
| SELECT / SHOW | 安全 | 无 |
| INSERT / UPDATE / DELETE | 中 | 先 SELECT 用同样 WHERE 看影响行数 |
| ALTER TABLE | 高 | 先看表大小,大表用 pt-online-schema-change |
| DROP / TRUNCATE | 极高 | 先备份,且二次确认库名 |
| GRANT / REVOKE | 中 | MySQL 8 不再支持 GRANT 隐式建用户 |
保命配置:给自己开一个不带自动提交的会话习惯。
SET autocommit = 0; -- 手动提交,改完先 SELECT 看结果再 COMMIT / ROLLBACK
-- 或者开启安全模式,禁止无 WHERE 的 UPDATE/DELETE(仅命令行客户端有效)
SET sql_safe_updates = 1;
👉 命令记不全查 Linux 命令速查;慢查询日志分析用 日志分析工具。
二、连接与基本信息
# 连接
mysql -h 127.0.0.1 -P 3306 -u root -p
mysql -u root -p -e "SELECT VERSION();" # 非交互执行单条
mysql -u root -p --database=app < init.sql # 执行脚本
mysql -u root -p --default-character-set=utf8mb4
SELECT VERSION(); -- 版本
SELECT @@version_comment; -- 发行版(MySQL / Percona / MariaDB)
SELECT DATABASE(), USER(), @@port, @@datadir;
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE '%time_zone%';
SELECT @@sql_mode; -- 严不严格,迁移时经常打架
SHOW ENGINES;
字符集建议全链路 utf8mb4(注意不是 utf8,MySQL 的 utf8 是 3 字节的伪 UTF-8,存不了 emoji):
-- 检查有没有踩坑
SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_COLLATION
FROM information_schema.TABLES
WHERE TABLE_COLLATION LIKE 'utf8_%' AND TABLE_SCHEMA NOT IN ('mysql','sys','performance_schema');
三、库与表
-- 建库(务必显式指定字符集)
CREATE DATABASE app DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
-- 建表模板
CREATE TABLE `user` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`username` VARCHAR(64) NOT NULL,
`mobile` CHAR(11) NOT NULL DEFAULT '',
`status` TINYINT NOT NULL DEFAULT 1,
`balance` DECIMAL(12,2) NOT NULL DEFAULT 0.00,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`),
KEY `idx_created` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 查看
SHOW DATABASES;
SHOW TABLES;
SHOW CREATE TABLE `user`\G -- \G 竖排显示,看建表语句必用
DESC `user`;
SHOW TABLE STATUS LIKE 'user'\G -- 行数(近似)、数据大小、引擎
-- 改表
ALTER TABLE `user` ADD COLUMN `email` VARCHAR(128) NOT NULL DEFAULT '' AFTER `mobile`;
ALTER TABLE `user` MODIFY COLUMN `mobile` VARCHAR(20) NOT NULL DEFAULT '';
ALTER TABLE `user` DROP COLUMN `email`;
RENAME TABLE `user` TO `t_user`;
大表加字段的代价要先估:
-- 看表有多大(Data_length + Index_length)
SELECT
TABLE_NAME,
ROUND((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 1) AS 'Size(MB)',
TABLE_ROWS
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'app'
ORDER BY (DATA_LENGTH + INDEX_LENGTH) DESC LIMIT 10;
超过几 GB 的表直接 ALTER 会锁表很久,MySQL 8 支持 ALGORITHM=INSTANT(加列到末尾等有限场景),否则用 pt-online-schema-change 或 gh-ost。
四、用户与权限(MySQL 8 语法变了)
最大的坑:MySQL 8.0 之后 GRANT 不再隐式创建用户,必须先 CREATE USER。
-- ✅ MySQL 8 正确写法
CREATE USER 'app'@'10.0.%' IDENTIFIED BY 'Str0ng_Pass!';
GRANT SELECT, INSERT, UPDATE, DELETE ON app.* TO 'app'@'10.0.%';
-- ❌ 这会报错(8.0 不再支持)
GRANT ALL ON app.* TO 'app'@'%' IDENTIFIED BY 'xxx';
-- 建只读账号
CREATE USER 'reader'@'%' IDENTIFIED BY 'Read_0nly!';
GRANT SELECT ON app.* TO 'reader'@'%';
-- 查看与回收
SHOW GRANTS FOR 'app'@'10.0.%';
REVOKE DELETE ON app.* FROM 'app'@'10.0.%';
DROP USER 'app'@'10.0.%';
密码与认证插件:
-- 8.0 默认 caching_sha2_password,老客户端(PHP 5.x、老 JDBC)连不上
ALTER USER 'app'@'%' IDENTIFIED WITH mysql_native_password BY 'Str0ng_Pass!';
-- 或全局改回(my.cnf)
-- default_authentication_plugin=mysql_native_password
-- 改密码
ALTER USER 'app'@'%' IDENTIFIED BY 'New_Pass!';
-- root 忘记密码时(需重启):mysqld --skip-grant-tables --skip-networking &
账号三原则:最小权限、限定来源网段、不同应用不同账号(出问题时能立刻定位是哪个服务)。
五、增删改查(带安全习惯)
-- 危险操作先预演:把 UPDATE 的 WHERE 拿去 SELECT 数一遍
SELECT COUNT(*) FROM `user` WHERE status = 0; -- 先确认影响范围
UPDATE `user` SET status = 9 WHERE status = 0; -- 再改
-- 带 LIMIT 的删除,避免一次锁太多行
DELETE FROM log WHERE created_at < '2026-01-01' LIMIT 5000;
-- 大批量删除用循环(每次 5000,间隔 0.5 秒)
-- 见下方脚本
-- 插入与冲突处理
INSERT INTO `user` (username, mobile) VALUES ('afei', '13800138000')
ON DUPLICATE KEY UPDATE mobile = VALUES(mobile);
-- 批量插入
INSERT INTO `user` (username, mobile) VALUES ('a','1'), ('b','2'), ('c','3');
-- 只改一行
UPDATE `user` SET balance = balance - 100 WHERE id = 1 LIMIT 1;
分批删除脚本(避免长事务和主从延迟):
#!/usr/bin/env bash
# 用法:./del.sh "created_at < '2026-01-01'"
where="${1:?用法: $0 \"条件\"}"
while : ; do
n=$(mysql -u root -p"$PW" -N -B -e "DELETE FROM app.log WHERE $where LIMIT 5000; SELECT ROW_COUNT();")
echo "删除 $n 行"
[ "$n" -eq 0 ] && break
sleep 0.5
done
六、导入导出
# 导出整个库(含建表语句,最常用)
mysqldump -u root -p --single-transaction --routines --triggers --events app > app.sql
# 只导结构
mysqldump -u root -p --no-data app > schema.sql
# 只导数据(tab 分隔,导入极快)
mysqldump -u root -p -t --tab=/tmp/app_data app
# 带条件导出
mysqldump -u root -p app `user` --where="id > 10000" > user_part.sql
# 压缩导出
mysqldump -u root -p --single-transaction app | gzip > app.sql.gz
# 导入
mysql -u root -p app < app.sql
gunzip < app.sql.gz | mysql -u root -p app
--single-transaction 是 InnoDB 不锁表导出的关键参数(一致性快照),没有它,导出期间会锁表。
导出成 CSV(给运营/财务看):
SELECT id, username, mobile, created_at
INTO OUTFILE '/tmp/user.csv'
FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n'
FROM `user`;
-- 若报 secure_file_priv 限制,查:SHOW VARIABLES LIKE 'secure_file_priv';
导入 CSV:
LOAD DATA INFILE '/tmp/user.csv'
INTO TABLE `user`
FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n'
(id, username, mobile, created_at);
七、连接、进程与锁(排障必查)
SHOW PROCESSLIST; -- 前 100 条,看谁在跑
SHOW FULL PROCESSLIST; -- 完整 SQL
SELECT * FROM information_schema.PROCESSLIST
WHERE COMMAND != 'Sleep' AND TIME > 10 ORDER BY TIME DESC;
-- 连接数水位(超过 max_connections 的 80% 就该告警)
SHOW VARIABLES LIKE 'max_connections';
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';
-- 按用户统计连接
SELECT USER, COUNT(*) FROM information_schema.PROCESSLIST GROUP BY USER;
-- 杀掉某个查询(只杀查询不杀连接用 KILL QUERY)
KILL QUERY 12345;
KILL 12345; -- 连连接一起杀
-- 当前锁等待
SELECT * FROM performance_schema.data_lock_waits;
SHOW ENGINE INNODB STATUS\G -- 看 LATEST DETECTED DEADLOCK 段
批量生成 kill 语句(连接打满时救命):
-- 杀掉 Sleep 超过 300 秒的连接
SELECT CONCAT('KILL ', id, ';')
FROM information_schema.PROCESSLIST
WHERE COMMAND = 'Sleep' AND TIME > 300 AND USER NOT IN ('root','system user');
八、运行状态速查
-- QPS / TPS(间隔 1 秒取差值)
SHOW GLOBAL STATUS LIKE 'Questions';
SHOW GLOBAL STATUS LIKE 'Com_commit';
SHOW GLOBAL STATUS LIKE 'Com_rollback';
-- 简化版:
SHOW GLOBAL STATUS WHERE Variable_name IN
('Questions','Com_select','Com_insert','Com_update','Com_delete','Slow_queries','Threads_connected');
-- InnoDB 缓冲池命中率(应 > 99%)
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
-- 命中率 = 1 - Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests
-- 临时表落盘比例(太高要调 tmp_table_size)
SHOW GLOBAL STATUS LIKE 'Created_tmp%';
-- 表缓存与打开表数
SHOW GLOBAL STATUS LIKE 'Open_tables';
SHOW GLOBAL VARIABLES LIKE 'table_open_cache';
一条汇总查询:
SELECT
(SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Uptime') AS 运行秒数,
(SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Questions') AS 总查询数,
(SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Slow_queries') AS 慢查询数,
(SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Threads_connected') AS 当前连接;
👉 慢查询日志暴涨会连带把磁盘写满,排查思路见 Linux 磁盘满了怎么排查;容量规划用 磁盘容量计算器。
九、常见误区
- 用
utf8而不是utf8mb4:存 emoji 直接Incorrect string value报错。 - MySQL 8 里用
GRANT ... IDENTIFIED BY建用户:语法直接报错,必须先CREATE USER。 mysqldump不加--single-transaction:导出期间锁表,业务全卡。DELETE一次删几百万行:长事务 + 大 undo + 主从延迟,应该分批。ALTER大表直接跑:锁表几十分钟,应该在低峰期或用在线改表工具。- 给应用账号
ALL PRIVILEGES且host='%':一次脱库就是全库沦陷。
十、几条纪律
- 任何
UPDATE/DELETE先写SELECT COUNT(*)用同一 WHERE 验证范围。 - 命令行客户端开
sql_safe_updates,或者显式BEGIN;…COMMIT;。 - 库表字符集统一
utf8mb4,sql_mode在环境间保持一致。 - 每个应用独立账号、限定网段、最小权限。
- 大表结构变更走低峰期 + 在线改表工具,先在从库演练。
- 导出必须带
--single-transaction,导入前确认目标库名。
十一、延伸阅读
👉 相关在线工具:Linux 命令速查 · 日志分析 · 磁盘容量计算,免登录、纯前端。