侧边栏壁纸
  • 累计撰写 24 篇文章
  • 累计创建 5 个标签
  • 累计收到 5 条评论

目 录CONTENT

文章目录

MySQL-常用操作-001

MySQL 常用操作手册

一句话总结: MySQL 是最流行的开源关系型数据库。这本手册汇总了运维日常最常用的 SQL 操作——从登录到备份,从查慢 SQL 到处理死锁,全部可直接复制执行。

目录


连接与登录

命令行登录

场景: 从跳板机直连 MySQL 实例,日常操作基础。

# 语法:mysql -h 主机 -P 端口 -u 用户名 -p
# -p 后面不加密码,回车后交互式输入(更安全,密码不会留在历史记录)
mysql -h 10.0.1.100 -P 3306 -u root -p

# 快速登录本地 Socket(比 TCP 更快)
mysql -u root -p -S /tmp/mysql.sock

预期输出:

Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
mysql>

参数说明:

参数 含义 示例
-h 主机地址 -h 10.0.1.100
-P 端口(默认 3306) -P 3306
-u 用户名 -u root
-p 密码提示 -p-p'密码'(不推荐明文)
-S Socket 文件路径 -S /tmp/mysql.sock

注意事项:

  • ⚠️ mysql -p密码 密码紧挨 -p 不能有空格,但不要把密码写在命令里——会被记录到 ~/.mysql_history
  • 💡 用 mysql_config_editor 可以安全存储登录凭据:mysql_config_editor set --login-path=prod --host=10.0.1.100 --user=root --password

查看连接信息

-- 查看当前所有活跃连接(排查连接池爆满时必用)
SHOW FULL PROCESSLIST;

-- 只看非 Sleep 的活跃查询
SELECT id, user, host, db, command, time, state, info
FROM information_schema.PROCESSLIST
WHERE command != 'Sleep';

预期输出(节选):

+----+------+-------------+------+---------+------+-----------+------------------+
| id | user | host        | db   | command | time | state     | info             |
+----+------+-------------+------+---------+------+-----------+------------------+
| 10 | root | 10.0.1.1:22 | test | Query   |    0 | executing | SELECT * FROM t  |
| 11 | app  | 10.0.1.2:33 | test | Sleep   |  120 |           | NULL             |
+----+------+-------------+------+---------+------+-----------+------------------+

库表管理

查看数据库和表

-- 查看所有数据库
SHOW DATABASES;

-- 选择数据库
USE mydb;

-- 查看当前库所有表
SHOW TABLES;

-- 查看表结构(字段、类型、主键等)
DESC mytable;
-- 或
SHOW CREATE TABLE mytable\G

预期输出(DESC):

+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| name  | varchar(100) | YES  |     | NULL    |                |
+-------+--------------+------+-----+---------+----------------+

创建数据库和表

-- 创建数据库(指定字符集,避免乱码)
CREATE DATABASE IF NOT EXISTS mydb
  DEFAULT CHARACTER SET utf8mb4
  DEFAULT COLLATE utf8mb4_unicode_ci;

-- 创建表
CREATE TABLE IF NOT EXISTS users (
    id       INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
    name     VARCHAR(100) NOT NULL COMMENT '用户名',
    email    VARCHAR(200) NOT NULL COMMENT '邮箱',
    age      TINYINT UNSIGNED DEFAULT 0 COMMENT '年龄',
    status   TINYINT DEFAULT 1 COMMENT '状态 1=正常 0=禁用',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    INDEX idx_email (email),
    INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

参数说明:

参数 含义 说明
ENGINE=InnoDB 存储引擎 生产环境必须用 InnoDB,支持事务和行锁
utf8mb4 字符集 支持 emoji 和所有 Unicode 字符
AUTO_INCREMENT 自增 主键自增,不需手动赋值
UNSIGNED 无符号 ID 用这个可以存更多正数

修改表结构

-- ⚠️ 大表加字段可能导致锁表,生产环境用 pt-online-schema-change

-- 添加字段
ALTER TABLE users ADD COLUMN phone VARCHAR(20) AFTER email;

-- 修改字段类型
ALTER TABLE users MODIFY COLUMN phone VARCHAR(30);

-- 重命名字段
ALTER TABLE users CHANGE COLUMN phone mobile VARCHAR(20);

-- 删除字段(谨慎!)
ALTER TABLE users DROP COLUMN mobile;

-- 添加索引
ALTER TABLE users ADD INDEX idx_name (name);

-- 查看表大小(排查磁盘空间)
SELECT
    table_name,
    ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'size_mb'
FROM information_schema.tables
WHERE table_schema = 'mydb'
ORDER BY size_mb DESC;

注意事项:

  • ⚠️ 在几百万行的大表上 ALTER TABLE 会锁全表——业务高峰期千万别跑
  • 💡 生产环境大表变更推荐用 pt-online-schema-change(Percona Toolkit)或 gh-ost

数据增删改查

在正式开始之前,你需要先了解两件事:

第一:我们先看样例表

下面的 SQL 例子都基于这两张表——一张存操作日志、一张存用户信息。

-- operation_logs:记录每一次 API 请求的日志
-- 就像:每次有人访问你的网站,MySQL 都会记一条——是谁、什么时候、调了什么接口、花了多久、报错了没有
CREATE TABLE operation_logs (
    id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    trace_id    VARCHAR(32)     NOT NULL COMMENT '链路追踪ID(一个请求的唯一身份证号)',
    app_name    VARCHAR(50)     NOT NULL COMMENT '哪个应用产生的日志?如 order-api(订单服务)',
    log_level   ENUM('DEBUG','INFO','WARN','ERROR','FATAL') NOT NULL DEFAULT 'INFO',
    message     TEXT            NOT NULL COMMENT '日志的具体内容(报错信息/描述)',
    req_ip      VARCHAR(45)     NOT NULL COMMENT '请求来源的 IP 地址',
    user_id     INT UNSIGNED    DEFAULT NULL COMMENT '操作用户ID(没登录就是 NULL)',
    api_path    VARCHAR(200)    DEFAULT NULL COMMENT '请求的接口路径,如 /api/order/create',
    status_code SMALLINT        DEFAULT NULL COMMENT 'HTTP 状态码(200=成功, 404=找不到, 500=服务器错误)',
    cost_ms     INT UNSIGNED    DEFAULT 0 COMMENT '这个请求花了多少毫秒',
    created_at  DATETIME(3)     NOT NULL COMMENT '日志产生的时间(精确到毫秒)',
    INDEX idx_app_level (app_name, log_level),
    INDEX idx_created (created_at),
    INDEX idx_user (user_id),
    INDEX idx_api_status (api_path(100), status_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='操作日志表';

-- users:用户基本信息
CREATE TABLE users (
    id         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name       VARCHAR(100) NOT NULL,
    role       ENUM('admin','ops','dev','viewer') NOT NULL DEFAULT 'dev',
    status     TINYINT DEFAULT 1 COMMENT '1=正常 0=禁用',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

第二:SQL 的执行顺序(非常关键,搞懂这个你就理解了 SQL 的灵魂)

很多人以为 SQL 是从上往下执行的——错了。SQL 的逻辑执行顺序是这样的:

FROM         → 第一步:从哪张表拿数据?
    ↓
WHERE        → 第二步:筛选哪些行留下?
    ↓
GROUP BY     → 第三步:要不要分组统计?
    ↓
HAVING       → 第四步:分组后还要不要过滤(很少用)?
    ↓
SELECT       → 第五步:我要看哪些列?
    ↓
ORDER BY     → 第六步:按什么排序?
    ↓
LIMIT        → 第七步:只要前 N 条

怎么记: “饭(FROM)碗(WHERE)群(GROUP BY)会(HAVING)选(SELECT)排(ORDER BY)列(LIMIT)”

理解这个顺序后,你就知道为什么 WHERE 里面不能写 COUNT(*)——因为 WHERE 执行的时候,GROUP BY 还没跑呢,怎么知道统计结果?


场景 1|按时间范围查日志 ⭐ 最高频

场景故事

今早 10:05 收到告警:“order-api 在 09:50~10:00 之间 500 错误飙高!”

你现在要:立刻查出那 10 分钟内所有 ERROR 日志,看看是什么报错、哪个接口、耗时多久。


SQL → 逐行拆解

-- 需求:查某段时间内、某应用的 ERROR 日志
SELECT trace_id, created_at, message, api_path, cost_ms, req_ip   ← 第⑤步:我只想看这 6 列
FROM   operation_logs                                               ← 第①步:从日志表里找
WHERE  app_name   = 'order-api'                                     ← 第②步:只留 order-api 的记录
  AND  log_level  = 'ERROR'                                         ← 第②步:只留 ERROR 级别的
  AND  created_at >= '2026-06-23 09:50:00'                          ← 第②步:只要 9:50 之后的
  AND  created_at <  '2026-06-23 10:00:00'                          ← 第②步:只要 10:00 之前的
ORDER BY created_at DESC;                                           ← 第⑥步:最新的排最上面

逐行解释:

SQL 片段 小白版解释 为什么要这么写
SELECT trace_id, created_at, ... “我只想看这些列” 不要写 SELECT *,只挑需要的列
FROM operation_logs “从日志表里找数据” 告诉 MySQL 去哪个表翻
WHERE app_name = 'order-api' “只看订单服务的日志” 系统可能有很多应用,不限定会查到别人的数据
AND log_level = 'ERROR' “只看错误级别的” 你和 INFO 混一起看是浪费时间
AND created_at >= '...' “只看 9:50 之后的” >=(大于等于)包含 9:50:00 这一秒本身
AND created_at < '...' “只看 10:00 之前的” < 不包含 10:00:00 整
ORDER BY created_at DESC “最新的在最上面” 排障时最新发生的往往最重要

思考过程:

Q:为什么用 >= 和 <,而不是 BETWEEN?
A:BETWEEN 包含两边,BETWEEN '09:50' AND '10:00' 会包含 10:00:00 整。
   用 >= AND < 叫"左闭右开区间",是时间范围查询的标准写法,不会多出下一秒的数据。

Q:为什么 ORDER BY created_at DESC 而不是 ASC?
A:DESC = 倒序(最新的在前)。排障时永远先看最近的——10:00 的 ERROR 比 09:50 的更有参考价值。

预期输出:

+----------------------------------+-------------------------+--------------------------------------+-------------------+--------+-------------+
| trace_id                         | created_at              | message                              | api_path          | cost_ms| req_ip      |
+----------------------------------+-------------------------+--------------------------------------+-------------------+--------+-------------+
| a1b2c3d4-e5f6-7890-abcd-ef123456 | 2026-06-23 09:59:58.123 | [Timeout] Redis read timeout after 3s| /api/order/create |   3210 | 10.0.1.50   |
| a1b2c3d4-e5f6-7890-abcd-ef789012 | 2026-06-23 09:55:32.456 | [DBError] Deadlock found when trying.| /api/order/pay    |   5432 | 10.0.1.88   |
+----------------------------------+-------------------------+--------------------------------------+-------------------+--------+-------------+

如何读懂:

  • 2 条 ERROR,都在 9:55~10:00 这 5 分钟
  • 第一条:创建订单接口调 Redis 超时(3.2 秒)
  • 第二条:支付接口出现死锁(5.4 秒)
  • 推理:可能 Redis 先扛不住,业务降级失败导致死锁

变体一:查"过去 N 分钟"(巡检脚本用)

场景: 写一个脚本每分钟跑一次,检查过去 5 分钟哪个接口错误最多。

SELECT COUNT(*) AS error_count, api_path
FROM   operation_logs
WHERE  app_name   = 'order-api'
  AND  log_level  = 'ERROR'
  AND  created_at >= NOW() - INTERVAL 5 MINUTE    -- NOW() 是"当前时间",减 5 分钟
GROUP BY api_path
ORDER BY error_count DESC;

新知识点:

关键词 小白版解释
COUNT(*) “数一数有多少行”——计数
GROUP BY api_path “把相同接口的日志归到一堆”
AS error_count “给 COUNT(*) 起个名字”
NOW() - INTERVAL 5 MINUTE “5 分钟前”——当前时间减去 5 分钟

什么时候用? 写监控脚本。Zabbix / Prometheus 每分钟调一次这个 SQL,如果 error_count 突增就告警。


变体二:查某个 trace 的完整调用链

场景: 告警里给了一个 trace_id,想看这个请求从头到尾发生了什么。

SELECT created_at, log_level, api_path, message, cost_ms
FROM   operation_logs
WHERE  trace_id = 'a1b2c3d4-e5f6-7890-abcd-ef123456'   -- 限定是这个请求
ORDER BY created_at ASC;                                   -- ASC = 正序(最早的在前)

思考过程:

Q:为什么这里用 ASC(正序)?
A:你要看的是"一个请求从开始到结束的全过程"——正序排列就像看录像带从头播到尾。
   刚才场景 1 用 DESC 是因为"哪个错误最新"。

Q:trace_id 是什么?
A:每个请求进系统时就分配一个唯一 ID。这个 ID 会传递到所有下游服务。
   拿着这个 ID,你就能把同一个请求在前端、网关、后端、数据库的日志全部串起来。


场景 2|聚合统计(排查拐点、看趋势)

场景故事

老板问:“昨天订单接口成功率多少?”
或者你在写周报:“这周每天 500 错误的变化趋势是什么?”

你不能一条一条看日志,需要 MySQL 按小时汇总


SQL → 逐行拆解

SELECT                                                                         ← 第⑤步
    DATE_FORMAT(created_at, '%Y-%m-%d %H:00') AS time_bucket,                -- 把时间切成"小时块"
    COUNT(*)                                   AS total_reqs,                 -- 这个小时总共有多少次请求
    SUM(IF(status_code >= 500, 1, 0))          AS error_5xx,                  -- 其中 500 以上的有多少次
    ROUND(AVG(cost_ms), 1)                     AS avg_cost_ms,                -- 平均耗时
    ROUND(MAX(cost_ms), 0)                     AS max_cost_ms,                -- 最大耗时(最慢的那次)
    CONCAT(ROUND(SUM(IF(status_code >= 500, 1, 0)) * 100.0 / COUNT(*), 2), '%') AS error_rate  -- 错误率
FROM   operation_logs                                                           ← 第①步
WHERE  app_name = 'order-api'                                                  ← 第②步
  AND  created_at >= NOW() - INTERVAL 24 HOUR                                  ← 第②步:过去 24 小时
GROUP BY DATE_FORMAT(created_at, '%Y-%m-%d %H:00')                             ← 第③步:按"小时"分组
ORDER BY time_bucket ASC;                                                      ← 第⑥步

新函数解释:

函数 小白版解释
DATE_FORMAT(created_at, '%Y-%m-%d %H:00') 把时间裁成整点。比如 2026-06-23 09:45:122026-06-23 09:00。9:01~9:59 都算"9 点"
COUNT(*) 数一数有多少行
SUM(IF(条件, 1, 0)) 条件成立记 1,否则记 0,然后求和——就是数符合条件的行
AVG(cost_ms) 平均耗时 = 总耗时 ÷ 请求数
ROUND(x, 1) 四舍五入保留 1 位小数
CONCAT(a, '%') 把数字后面加个百分号

错误率计算过程:

错误的请求数 × 100 ÷ 总请求数 = 错误率%

举例(10:00~10:59 这小时):
  总请求 4210 次,其中 380 次返回 500
  错误率 = 380 × 100 ÷ 4210 = 9.03%

预期输出:

+------------------+-----------+----------+-------------+------------+------------+
| time_bucket      | total_reqs| error_5xx| avg_cost_ms | max_cost_ms| error_rate |
+------------------+-----------+----------+-------------+------------+------------+
| 2026-06-22 09:00 |      3520 |       12 |         45.2 |       2100 | 0.34%      |
| 2026-06-22 10:00 |      4210 |      380 |        320.5 |       8910 | 9.03%      | ← 拐点!
| 2026-06-22 11:00 |      1080 |      420 |        980.0 |      15000 | 38.89%     | ← 扩大
+------------------+-----------+----------+-------------+------------+------------+

如何看懂这张表:

时间 说明 你该做什么
09:00 ✅ 正常——0.34% 错误率,45ms 平均耗时 这是基准线
10:00 🚨 错误率跳到 9%,耗时升到 320ms 拐点!去查 10:00 有没有发布
11:00 🔥 错误率 38%,耗时 1 秒 问题扩散,立即处理

什么时候用? 每天早晨看巡检看板、出周报、对比"现在和过去差多少"时。一眼看到拐点在哪、趋势是好转还是恶化。


变体:按天统计,比对最近两周

SELECT
    DATE(created_at)         AS day,         -- DATE() 只取年月日
    COUNT(*)                 AS total,
    SUM(IF(log_level='ERROR', 1, 0)) AS errors,
    ROUND(AVG(cost_ms), 0)   AS avg_latency
FROM   operation_logs
WHERE  app_name = 'order-api'
  AND  created_at >= CURDATE() - INTERVAL 14 DAY
GROUP BY DATE(created_at)
ORDER BY day DESC;

DATE() vs DATE_FORMAT():

  • DATE(created_at)2026-06-23(按天)
  • DATE_FORMAT(created_at, '%Y-%m-%d %H:00')2026-06-23 09:00(按小时)


场景 3|搜索日志内容(关键词模糊匹配)

场景故事

线上出了故障,你知道出错日志里包含 “Redis timeout”。

问题:日志表几千万行,你怎么从海量数据里捞出这几条


基础写法 + 性能警告

SELECT trace_id, created_at, log_level, message, api_path, cost_ms
FROM   operation_logs
WHERE  app_name = 'order-api'
  AND  created_at >= '2026-06-23 00:00:00'
  AND  message LIKE '%Redis timeout%'    -- 包含这个关键词(不管在哪)
ORDER BY created_at DESC
LIMIT 50;

LIKE 的三种模式:

写法 含义 走索引吗?
LIKE 'Redis%' 以 Redis 开头 ✅ 走
LIKE '%timeout' 以 timeout 结尾 ❌ 不走
LIKE '%Redis%' 中间包含 Redis ❌ 不走

⚠️ 为什么 % 在前面就不走索引?

索引就像书的目录,按拼音顺序排好。你想找"以 R 开头的词"→ 翻到 R 那页就行(走索引)。
但你想找"词中间包含 timeout 的"→ 目录不会帮你排这个,你只能从第一页翻到最后一页(全表扫描)。

正确的做法:

情况 1:MySQL 数据不多(几百万行)+ 时间范围小 → 可以 LIKE
情况 2:MySQL 数据多 + 经常搜关键词 → 上 Elasticsearch(ELK)
情况 3:临时查一下 → 缩小时间窗口,加更多 WHERE 条件

安全版(缩小范围 + 加过滤)

SELECT trace_id, message, api_path
FROM   operation_logs
WHERE  app_name = 'order-api'
  AND  created_at >= '2026-06-23 09:50:00'          -- ✅ 只搜 10 分钟
  AND  created_at <  '2026-06-23 10:00:00'
  AND  (message LIKE '%Redis%' OR message LIKE '%timeout%')   -- 多关键词
  AND  log_level IN ('ERROR', 'WARN')                 -- ✅ 只看 WARN 以上
ORDER BY created_at DESC;

什么时候用? 知道大致时间窗口,但不知道具体错误信息——用"时间 + 关键词 + 级别"三层过滤。


另一个实用变体:找昨天最慢的接口

SELECT api_path,
       COUNT(*)                AS call_count,
       ROUND(AVG(cost_ms), 0) AS avg_ms,
       ROUND(MAX(cost_ms), 0) AS max_ms
FROM   operation_logs
WHERE  app_name = 'order-api'
  AND  created_at >= CURDATE() - INTERVAL 1 DAY
GROUP BY api_path
HAVING call_count > 100                              -- 冷门接口不具统计意义,排除
ORDER BY avg_ms DESC
LIMIT 10;

为什么用 HAVING 而不是 WHERE?
call_count 是统计出来的(GROUP BY 之后才有),所以不能写在 WHERE 里。
回顾执行顺序:WHERE(第②步) → GROUP BY(第③步) → HAVING(第④步)

什么时候用? 系统变慢了→先查"哪个接口最慢"→先定位再优化



场景 4|多表关联查(JOIN)

场景故事

user_id=12345 一直在报错。日志表只存了 user_id(数字),你想知道这个人的姓名和角色——需要把日志表和用户表关联起来


JOIN 入门

日志表(左)              用户表(右)
+--------+--------+     +--------+--------+--------+
| log_id | user_id|     | id     | name   | role   |
+--------+--------+     +--------+--------+--------+
| L01    | 12345  |     | 12345  | 张三   | admin  |
| L02    | 12346  |     | 12346  | 李四   | dev    |
+--------+--------+     +--------+--------+--------+

JOIN 的意思:"把 user_id 对上 id,两边拼在一起"

结果:
+--------+--------+--------+--------+--------+
| log_id | user_id| id     | name   | role   |
+--------+--------+--------+--------+--------+
| L01    | 12345  | 12345  | 张三   | admin  |
| L02    | 12346  | 12346  | 李四   | dev    |
+--------+--------+--------+--------+--------+

SQL → 逐行拆解

SELECT
    l.created_at, l.log_level, l.api_path, l.message,          -- 来自日志表(l)
    u.name AS user_name, u.role AS user_role, u.status AS user_status  -- 来自用户表(u)
FROM   operation_logs l       -- l 是简称
JOIN   users u ON l.user_id = u.id   -- 🔑 关键:把 l.user_id 和 u.id 对上
WHERE  l.user_id = 12345
  AND  l.log_level IN ('ERROR', 'WARN')
  AND  l.created_at >= '2026-06-01'
ORDER BY l.created_at DESC
LIMIT 20;
代码 小白版解释
operation_logs l 给表起了短名叫 l
JOIN users u ON l.user_id = u.id “把两个表黏在一起——条件是日志的 user_id = 用户的 id”
u.name AS user_name “用户表的 name,在结果里改名叫 user_name”

JOIN vs LEFT JOIN:

JOIN(INNER JOIN):两边都有的才保留
  日志的 user_id=12345,用户表有 → 保留
  日志的 user_id=NULL        → 丢掉这条日志

LEFT JOIN:左边表全部保留,右边有就拼上,没有就 NULL
  日志的 user_id=NULL        → 保留,但 user_name 显示 NULL

什么时候用? 用户投诉"我操作不了"→拿 user_id 一查,看具体报错、是不是被禁了。


变体:查某个 IP 干了什么

场景: 安全同事说 “10.0.1.50 在扫我们的接口”。

SELECT l.created_at, l.api_path, l.status_code, l.message, u.name, u.role
FROM   operation_logs l
LEFT JOIN users u ON l.user_id = u.id      -- 用 LEFT JOIN!
WHERE  l.req_ip = '10.0.1.50'
  AND  l.created_at >= '2026-06-23 09:30:00'
  AND  l.created_at <  '2026-06-23 10:00:00'
ORDER BY l.created_at ASC;

为什么用 LEFT JOIN? 刷接口的请求可能都是未登录的(user_id = NULL)。用 JOIN 就丢掉了——你看不到这些请求。用 LEFT JOIN 会保留它们。



场景 5|子查询与 CTE(分步做复杂查询)

场景故事

你要找"昨天调用次数多、且错误率超过 5% 的接口"。

这是两步操作

  1. 先算每个接口的总调用数和错误数
  2. 再从结果里挑出错误率 > 5% 的

写法一:子查询

SELECT api_path, total, errors,
       ROUND(errors * 100.0 / total, 2) AS error_rate
FROM (
    -- 里面先算每个接口的总数和错误数
    SELECT api_path,
           COUNT(*) AS total,
           SUM(IF(status_code >= 500, 1, 0)) AS errors
    FROM operation_logs
    WHERE app_name = 'order-api'
      AND created_at >= CURDATE() - INTERVAL 1 DAY
    GROUP BY api_path
) AS stats                            -- 临时表,起名叫 stats
WHERE errors * 100.0 / total > 5       -- 在外面过滤
ORDER BY errors DESC;

执行顺序:

第①步(先执行括号里的):
  FROM operation_logs → WHERE → GROUP BY → 算出临时表 stats
  +---------------------+-------+--------+
  | api_path            | total | errors |
  +---------------------+-------+--------+
  | /api/order/create   | 35210 |   2850 |
  | /api/payment/pay    | 18200 |   1200 |
  | /api/user/login     | 88000 |    400 |
  +---------------------+-------+--------+

第②步(执行外层的):
  FROM stats → WHERE errors/total > 5% → SELECT 并计算 error_rate
  +---------------------+-------+--------+------------+
  | /api/order/create   | 35210 |   2850 |      8.09% |
  | /api/payment/pay    | 18200 |   1200 |      6.59% |
  +---------------------+-------+--------+------------+
  /api/user/login 调了 88000 次但只错 400 次(0.45%),被过滤掉

写法二:CTE(可读性更好)

WITH api_stats AS (                           -- 先定义一个"临时视图"
    SELECT api_path,
           COUNT(*) AS total,
           SUM(IF(status_code >= 500, 1, 0)) AS errors
    FROM operation_logs
    WHERE app_name = 'order-api'
      AND created_at >= CURDATE() - INTERVAL 1 DAY
    GROUP BY api_path
)
SELECT api_path, total, errors,
       ROUND(errors * 100.0 / total, 2) AS error_rate
FROM   api_stats
WHERE  errors * 100.0 / total > 5
ORDER BY errors DESC;

CTE vs 子查询:

对比 子查询 CTE(WITH)
可读性 括号多了眼花 先定义后使用,像写文章分段落
复用 不能用两次 可以多次引用

CTE 的复用优势:

WITH api_stats AS (...)
SELECT * FROM api_stats
WHERE errors * 100.0 / total > 5      -- 条件 1:错误率高
   OR avg_ms > 1000                    -- 条件 2:平均耗时 > 1 秒


场景 6|CASE WHEN 条件表达式(自定义分类)

场景故事

你想统计过去一周的日志,但不想按 MySQL 自带的 log_level 分类。
你想要:FATAL+ERROR 算"严重"、WARN 算"警告"、其他的算"正常"


SQL → 逐行拆解

SELECT
    CASE                                                          -- "如果...就..."
        WHEN log_level IN ('FATAL', 'ERROR') THEN '严重'             -- FATAL 或 ERROR → 严重
        WHEN log_level = 'WARN'               THEN '警告'             -- WARN → 警告
        ELSE                                                  '正常'  -- 剩下的 → 正常
    END AS severity,                                                -- 结果起名叫 severity
    COUNT(*) AS cnt
FROM   operation_logs
WHERE  app_name = 'order-api'
  AND  created_at >= NOW() - INTERVAL 7 DAY
GROUP BY severity
ORDER BY cnt DESC;

CASE WHEN 是怎么工作的:

MySQL 逐行读日志,对每一行:
  第 1 行:log_level = 'INFO'
    → 检查 WHEN IN ('FATAL','ERROR') → 不是
    → 检查 WHEN = 'WARN' → 不是
    → ELSE → '正常'

  第 2 行:log_level = 'FATAL'
    → 检查 WHEN IN ('FATAL','ERROR') → 是!→ '严重'(跳过后面)

  第 3 行:log_level = 'WARN'
    → 检查 WHEN IN ('FATAL','ERROR') → 不是
    → 检查 WHEN = 'WARN' → 是!→ '警告'

然后 GROUP BY severity 把相同标签的归一堆,COUNT(*) 数每堆多少条。

预期输出:

+----------+-------+
| severity | cnt   |
+----------+-------+
| 正常     | 285000|
| 警告     |   3200|
| 严重     |    480|
+----------+-------+

什么时候用? 周报/日报统计时——MySQL 自带的 GROUP BY log_level 只能分出 DEBUG/INFO/WARN/ERROR,但业务上你可能需要"重要/不重要"等自定义分类。


变体:把 HTTP 状态码转成人话

SELECT
    CASE
        WHEN status_code >= 200 AND status_code < 300 THEN '2xx 成功'
        WHEN status_code >= 400 AND status_code < 500 THEN '4xx 客户端错误'  -- 用户的问题
        WHEN status_code >= 500 THEN '5xx 服务端错误'                          -- 我们的问题
        ELSE '其他'
    END AS status_category,
    COUNT(*) AS cnt,
    CONCAT(ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 1), '%') AS pct
FROM   operation_logs
WHERE  app_name = 'order-api'
  AND  created_at >= CURDATE()
GROUP BY status_category
ORDER BY cnt DESC;

新知识点: SUM(COUNT(*)) OVER() = “把所有 COUNT(*) 加起来得到总数”。比如三组分别是 20000、500、100,总和 = 20600,第一组占比 = 20000/20600 ≈ 97.1%。



场景 7|窗口函数(在组内排名)

❗ 小白预警: 窗口函数稍微难一些。如果刚开始学 SQL,先跳过这个场景,后面再回来看。

场景故事

你想知道:“昨天每个小时里,错误数最多的 TOP 3 接口是哪些?”

普通 GROUP BY 只能算总数,算不出"每个小时内排第几名"——这需要窗口函数。


什么是窗口函数?一个例子看懂

普通 GROUP BY:

9:00 组 → /api/order/create  45次
9:00 组 → /api/order/pay     30次
9:00 组 → /api/user/login     8次

窗口函数多了一列"组内排名":

9:00 组 → /api/order/create  45次  排名 1
9:00 组 → /api/order/pay     30次  排名 2
9:00 组 → /api/user/login     8次  排名 3

格式:

ROW_NUMBER() OVER (
    PARTITION BY 分组依据     ← "在每个组内"
    ORDER BY 排序依据         ← "按什么排序来排名"
) AS 排名

SQL → 逐行拆解

WITH hourly_errors AS (
    SELECT
        DATE_FORMAT(created_at, '%Y-%m-%d %H:00') AS hour_bucket,
        api_path,
        COUNT(*) AS error_count,
        ROW_NUMBER() OVER (                                          -- 🆕 窗口函数
            PARTITION BY DATE_FORMAT(created_at, '%Y-%m-%d %H:00')  -- 按"小时"分组
            ORDER BY COUNT(*) DESC                                  -- 错误数多的排前面
        ) AS rn                                                      -- 排名结果叫 rn
    FROM operation_logs
    WHERE app_name = 'order-api'
      AND log_level IN ('ERROR', 'FATAL')
      AND created_at >= CURDATE() - INTERVAL 1 DAY
    GROUP BY hour_bucket, api_path
)
SELECT hour_bucket, api_path, error_count
FROM   hourly_errors
WHERE  rn <= 3                                                      -- 只要前 3 名
ORDER BY hour_bucket DESC, rn ASC;

执行过程(三步):

第①步:GROUP BY 算出每小时每个接口的错误数
第②步:ROW_NUMBER() 在每个小时组内排名
  → 9:00 组:create=1, pay=2, login=3, refund=4
  → 8:00 组:create=1, refund=2
第③步:WHERE rn <= 3 → 过滤掉排名 4 以后的

预期输出:

+------------------+---------------------+-------------+
| hour_bucket      | api_path            | error_count |
+------------------+---------------------+-------------+
| 2026-06-22 09:00 | /api/order/create   |          45 |
| 2026-06-22 09:00 | /api/order/pay      |          30 |
| 2026-06-22 09:00 | /api/user/login     |           8 |
| 2026-06-22 08:00 | /api/order/create   |          12 |
| 2026-06-22 08:00 | /api/order/refund   |           5 |
+------------------+---------------------+-------------+

什么时候用? 需要"每组里的前 N 个"——每个接口最慢的请求、每个小时的 TOP 错误、每个用户的首次登录。普通 GROUP BY 做不到。



场景 8|分页与大数据量查询的坑

场景故事

你要导出上个月所有 ERROR 日志。数据大概有几十万行。

如果你写 LIMIT 100 OFFSET 100000 → MySQL 会先查 100100 行,丢掉前 100000 行,只留 100 行——超级慢!


三种方案对比

-- ❌ 不要这样写!OFFSET 越大越慢
SELECT * FROM operation_logs WHERE log_level = 'ERROR'
ORDER BY id LIMIT 100 OFFSET 100000;

-- ✅ 方案一:游标分页(用 id 定位)
-- 第 1 页:
SELECT * FROM operation_logs WHERE log_level = 'ERROR'
ORDER BY id LIMIT 100;                    -- 最后一条 id = 100100

-- 第 2 页(记住上次的位置):
SELECT * FROM operation_logs
WHERE log_level = 'ERROR'
  AND id > 100100                        -- 🔑 直接从 100100 之后查
ORDER BY id LIMIT 100;

-- ✅ 方案二:按时间分批(导出数据最实用)
SELECT * FROM operation_logs
WHERE created_at >= '2026-06-01'
  AND created_at <  '2026-06-02'          -- 每次拿 1 天
ORDER BY id ASC;
方案 适用场景 性能
LIMIT x OFFSET y 小数据量管理后台
WHERE id > last 数据导出、API 翻页 ✅ 优秀
WHERE created_at 日志导出脚本 ✅ 优秀


插入(INSERT)

场景故事

数据导入脚本,要把日志从备份恢复到 MySQL。

-- 单条插入:一次加一行
INSERT INTO operation_logs
    (trace_id, app_name, log_level, message, req_ip, api_path, cost_ms, created_at)
VALUES
    ('manual-001', 'order-api', 'INFO', '手动回放请求确认修复', '127.0.0.1', '/api/check', 0, NOW());

-- 批量插入:一次加多条(比逐条快 50 倍)
INSERT INTO operation_logs (trace_id, app_name, log_level, message, req_ip, api_path, cost_ms, created_at)
VALUES
    ('batch-001', 'order-api', 'ERROR', 'DB connection timeout', '10.0.1.1', '/api/order', 3210, NOW()),
    ('batch-002', 'order-api', 'WARN',  'Slow query detected',  '10.0.1.2', '/api/user', 2100, NOW()),
    ('batch-003', 'order-api', 'INFO',  'Request completed',    '10.0.1.3', '/api/ok',     45, NOW());

注意事项:

  • ⚠️ 批量控制在 500~1000 条一次,太大导致 binlog 暴涨、主从延迟
  • ⚠️ 日志表每秒写入量大时,建议按天/月分区,否则单表几亿行 INSERT 也会慢

更新(UPDATE)——安全第一

🚨 铁律:先 SELECT 再 UPDATE

-- ❌ 错误:忘写 WHERE → 全表更新!没有后悔药
-- UPDATE operation_logs SET log_level = 'ERROR';

-- ✅ 正确:
-- 第①步:先 SELECT 确认
SELECT id, log_level FROM operation_logs WHERE id = 12345;
-- 第②步:确认无误再 UPDATE
UPDATE operation_logs SET log_level = 'ERROR' WHERE id = 12345;

-- 批量更新(记得加时间范围)
UPDATE operation_logs
SET log_level = 'DEBUG'
WHERE req_ip = '10.0.1.100'
  AND created_at >= '2026-06-01';   -- ✅ 加时间范围,避免全表锁


删除(DELETE)——最危险的操作

场景故事

日志表 500GB,磁盘告警。你要清理 90 天前的数据。

❌ 新手写:DELETE FROM operation_logs WHERE created_at < '2026-01-01'
→ 删几千万行 → ① 锁大量行 ② 撑爆 binlog ③ 主从延迟数小时

正确做法:分批删除

-- 每次只删 1 万行,跑完等 1 秒再下一批
DELETE FROM operation_logs
WHERE created_at < '2026-01-01'
LIMIT 10000;

-- Shell 循环脚本:
-- while true; do
--   mysql -e "DELETE FROM operation_logs WHERE created_at < '2026-01-01' LIMIT 10000;"
--   sleep 1
-- done

最优方案:建表时按时间分区

-- 建分区表(按月)
CREATE TABLE operation_logs_partitioned (
    id BIGINT UNSIGNED AUTO_INCREMENT,
    -- ...
    created_at DATETIME(3) NOT NULL,
    PRIMARY KEY (id, created_at)    -- 分区键必须包含在主键中!
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE (TO_DAYS(created_at)) (
    PARTITION p202606 VALUES LESS THAN (TO_DAYS('2026-07-01')),
    PARTITION p202607 VALUES LESS THAN (TO_DAYS('2026-08-01')),
    PARTITION p202608 VALUES LESS THAN (TO_DAYS('2026-09-01')),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

-- 清理旧分区:秒级!
ALTER TABLE operation_logs_partitioned DROP PARTITION p202606;

三种清理方案对比:

方案 速度 对业务影响
DELETE ... LIMIT 10000 循环 慢但安全 ✅ 几乎无影响
TRUNCATE TABLE 瞬间 ⚠️ 需停写
DROP PARTITION 秒级 无影响

什么时候用分区? 日志表 > 100GB 或每天新增 > 100 万行时,一定要分区。清理从小时级变秒级。



日志查询思维导图(一张表总结)

你的需求 所用技术 核心注意点
🔴 实时排障,查最近日志 WHERE created_at >= + ORDER BY DESC LIMIT 必须带时间范围 + 走索引
📈 看趋势、做周报 GROUP BY DATE_FORMAT(...) + SUM(IF()) 超大量数据用汇总表
🔍 搜日志关键词 LIKE '%keyword%' + 缩小时间窗 大日志用 ES,别硬查 MySQL
👤 查某用户/IP WHERE user_id = x / WHERE trace_id = x 对应字段必须建索引
🐌 查最慢的接口 GROUP BY + AVG(cost_ms) + ORDER BY DESC 先过滤冷门接口(HAVING)
🗑️ 清理过期日志 DELETE LIMIT N 循环 或 DROP PARTITION 永远不要一把删千万行
🔗 日志 + 业务信息关联 JOIN / LEFT JOIN 未登录的用 LEFT JOIN
🏆 每组里的前 N 个 ROW_NUMBER() OVER (PARTITION BY ...) 窗口函数,普通 GROUP BY 做不到
📋 分批导出大数据 WHERE id > lastWHERE created_at BETWEEN 不要用 OFFSET

索引管理

是什么: 索引就像书的目录——没有索引就是逐页翻书(全表扫描)。

查看索引

-- 查看表的索引
SHOW INDEX FROM users;

-- 找出没有主键的表(运维巡检用)
SELECT table_schema, table_name
FROM information_schema.tables
WHERE engine = 'InnoDB'
  AND table_schema NOT IN ('mysql', 'sys', 'performance_schema', 'information_schema')
  AND (SELECT COUNT(*) FROM information_schema.statistics
       WHERE table_schema = information_schema.tables.table_schema
         AND table_name = information_schema.tables.table_name
         AND index_name = 'PRIMARY') = 0;

添加和删除索引

-- 添加普通索引
CREATE INDEX idx_email ON users(email);

-- 添加唯一索引
CREATE UNIQUE INDEX idx_email_unique ON users(email);

-- 添加复合索引(最左前缀原则)
-- 场景:经常按 status + created_at 查询
CREATE INDEX idx_status_created ON users(status, created_at);

-- 删除索引
DROP INDEX idx_email ON users;

-- 查看索引使用情况(排查慢查询)
SELECT * FROM sys.schema_unused_indexes;

索引优化原则

原则 说明 反例
最左前缀 复合索引 (a,b,c) 能走 (a)(a,b)(a,b,c) WHERE b=1 不走
区分度优先 性别等低区分度字段不适合单列索引 WHERE gender='M' 走索引也慢
不要过多索引 写性能下降,占用磁盘 一张表超过 5~8 个索引要警惕
覆盖索引 查询的列都在索引里,不回表查

💡 如何确认查询是否走索引:

EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';
-- 看 type 列:const > ref > range > index > ALL(ALL 就是全表扫描)

用户与权限

创建用户

-- 创建用户(密码用强密码)
CREATE USER 'app_user'@'10.0.1.%' IDENTIFIED BY 'StrongP@ss123';

-- 查看所有用户
SELECT user, host, account_locked FROM mysql.user;

参数说明:

参数 含义 建议
'app_user'@'10.0.1.%' 用户名@允许登录的 IP 段 不要用 % 允许所有 IP
IDENTIFIED BY 密码 至少 16 位,含大小写 + 数字 + 特殊字符

授权

-- 授予库级权限(最小权限原则)
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app_user'@'10.0.1.%';

-- 授予只读账号(报表查询专用)
GRANT SELECT ON mydb.* TO 'readonly'@'10.0.1.%';

-- 授予 DBA 权限(仅限个人账号)
GRANT ALL PRIVILEGES ON *.* TO 'dba_zhangsan'@'10.0.1.%' WITH GRANT OPTION;

-- 刷新权限(修改权限后必须执行)
FLUSH PRIVILEGES;

-- 查看用户权限
SHOW GRANTS FOR 'app_user'@'10.0.1.%';

撤销权限和删除用户

-- 撤销权限
REVOKE DELETE ON mydb.* FROM 'app_user'@'10.0.1.%';

-- 删除用户
DROP USER 'old_user'@'10.0.1.%';

注意事项:

  • ⚠️ 不要用 root 给应用用! 给应用的最小权限:SELECT, INSERT, UPDATE, DELETE
  • ⚠️ 权限变更后一定要 FLUSH PRIVILEGES
  • 💡 用 SHOW GRANTS 定期审计用户权限

备份与恢复

mysqldump 逻辑备份

# 备份单个库
mysqldump -h 10.0.1.100 -u root -p --single-transaction --routines --triggers --events mydb > mydb_20260623.sql

# 备份多个库
mysqldump -h 10.0.1.100 -u root -p --single-transaction --databases db1 db2 db3 > multi_db.sql

# 备份全部库
mysqldump -h 10.0.1.100 -u root -p --single-transaction --all-databases --routines > all_dbs.sql

# 只导结构(不要数据)
mysqldump -h 10.0.1.100 -u root -p --no-data mydb > mydb_schema.sql

参数说明:

参数 含义 必加?
--single-transaction 不加锁读一致性快照(InnoDB) 一定要加
--routines 导出存储过程和函数 ✅ 推荐
--triggers 导出触发器 ✅ 推荐
--no-data 只导结构不要数据 按需
--where 条件导出部分数据 按需

恢复

# 恢复单个库
mysql -h 10.0.1.100 -u root -p mydb < mydb_20260623.sql

# 或者在 MySQL 内执行
mysql> source /backup/mydb_20260623.sql;

快速查看备份文件内容

# 只看备份中的建表语句,不看数据
grep "^CREATE TABLE" mydb_20260623.sql | head -10

# 查看备份文件大小
ls -lh mydb_20260623.sql

注意事项:

  • ⚠️ 恢复前一定要确认目标库为空或已做好备份! 恢复会覆盖数据
  • ⚠️ 不要在生产高峰期做 mysqldump,即使 --single-transaction 也有额外开销
  • 💡 数据量 > 50GB 时考虑用 XtraBackup(物理备份) 替代 mysqldump,速度和恢复时间快得多

性能排查

查慢查询

-- 开启慢查询日志(临时,当前会话有效)
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 2;  -- 超过 2 秒的记录
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';

-- 查看当前正在执行的查询(排查"卡住"的情况)
SELECT * FROM sys.session
WHERE command != 'Sleep' AND time > 10;

-- 查看最近 10 条慢查询(需要 performance_schema)
SELECT * FROM sys.statement_analysis
WHERE avg_latency > 100000000000  -- 大于 100 秒
ORDER BY avg_latency DESC
LIMIT 10;

排查步骤:

-- 1. 找到慢查询的 SQL(从慢查询日志)
-- 2. 用 EXPLAIN 分析执行计划
EXPLAIN SELECT * FROM orders WHERE user_id = 123 ORDER BY created_at DESC;

-- 3. 看 type 和 rows 字段
--    type=ALL → 全表扫描,需要加索引
--    rows=100000 → MySQL 扫描了 10 万行,很大

-- 4. 看是否有文件排序
--    Extra: "Using filesort" → 排序字段没索引
--    加复合索引:CREATE INDEX idx_user_created ON orders(user_id, created_at);

查看表大小和碎片

-- 查看各库磁盘占用
SELECT
    table_schema AS '数据库',
    ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS '总大小(MB)',
    ROUND(SUM(data_length) / 1024 / 1024, 2) AS '数据(MB)',
    ROUND(SUM(index_length) / 1024 / 1024, 2) AS '索引(MB)'
FROM information_schema.tables
GROUP BY table_schema
ORDER BY SUM(data_length + index_length) DESC;

-- 查看碎片率(碎片率 > 30% 考虑 OPTIMIZE)
SELECT
    table_name,
    ROUND(data_free / 1024 / 1024, 2) AS '碎片大小(MB)',
    ROUND(data_free * 100 / (data_length + index_length), 2) AS '碎片率(%)'
FROM information_schema.tables
WHERE table_schema = 'mydb' AND data_free > 0
ORDER BY data_free DESC;

查看当前正在跑的事务和锁

-- 查看当前事务(排查事务未提交导致的锁)
SELECT * FROM information_schema.INNODB_TRX\G

-- 查看当前锁等待(排查死锁)
SELECT * FROM sys.innodb_lock_waits;

-- 强制结束长时间未提交的事务(最后手段)
-- KILL 查询线程 ID(从 PROCESSLIST 查到的 id)
KILL 12345;

注意事项:

  • ⚠️ KILL 会回滚事务,可能导致数据不一致,确认清楚再执行
  • 💡 定期巡检:sys.session 表里 command != 'Sleep' AND time > 300 的查询需要排查

锁与事务

事务控制

-- 开启事务
START TRANSACTION;

-- 执行操作
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

-- 提交(成功)
COMMIT;

-- 或回滚(失败)
ROLLBACK;

查看事务隔离级别

-- 查看当前隔离级别(默认 REPEATABLE-READ)
SELECT @@transaction_isolation;

-- 修改隔离级别(当前会话)
SET SESSION transaction_isolation = 'READ-COMMITTED';

四种隔离级别:

级别 脏读 不可重复读 幻读 场景
READ UNCOMMITTED 几乎不用
READ COMMITTED 大部分业务(如 Oracle 默认)
REPEATABLE READ MySQL 默认
SERIALIZABLE 极少数强一致性场景

死锁排查

-- 查看最近一次死锁日志
SHOW ENGINE INNODB STATUS\G

-- 查看当前锁等待信息
SELECT * FROM sys.innodb_lock_waits;

预期输出(死锁日志关键信息):

------------------------
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:  // 事务1:等待 A 锁
*** (2) TRANSACTION:  // 事务2:等待 B 锁,持有 A 锁
*** WE ROLL BACK TRANSACTION (2)  // 自动回滚事务2

💡 预防死锁:

  • 多个事务按相同顺序访问表(例如都先操作 account_a 再操作 account_b)
  • 缩短事务执行时间,不要在一个事务里做大量查询
  • 使用 READ COMMITTED 隔离级别可以减少间隙锁导致的死锁

常用运维命令速查

用途 命令 说明
查看版本 SELECT VERSION(); 确认 MySQL 版本
查看运行时间 SHOW STATUS LIKE 'Uptime'; 数据库持续运行秒数
查看最大连接数 SHOW VARIABLES LIKE 'max_connections'; 默认 151,生产一般 500~2000
当前连接数 SHOW STATUS LIKE 'Threads_connected'; 当前使用连接数
查看字符集 SHOW VARIABLES LIKE 'character_set_%'; 确认是否 utf8mb4
查看 binlog 状态 SHOW MASTER STATUS; 确认主库 binlog 文件名和位置
查看主从状态 SHOW SLAVE STATUS\G 检查主从同步是否正常
查看数据库文件路径 SHOW VARIABLES LIKE 'datadir'; 数据文件存放目录
查看最大允许包大小 SHOW VARIABLES LIKE 'max_allowed_packet'; 默认 64M,大字段需调大
查看错误日志位置 SHOW VARIABLES LIKE 'log_error'; 排查异常的重要文件

快速健康检查命令

# 一键检查 MySQL 健康状态(返回关键指标,适合写入监控脚本)
mysqladmin -h 10.0.1.100 -u root -p ping
mysqladmin -h 10.0.1.100 -u root -p status
mysqladmin -h 10.0.1.100 -u root -p extended-status

# 或者 Shell 脚本方式
mysql -h 10.0.1.100 -u root -p -e "
SELECT 'Uptime' as k, VARIABLE_VALUE as v FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Uptime'
UNION ALL
SELECT 'Connections', VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Threads_connected'
UNION ALL
SELECT 'Slow_queries', VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Slow_queries';
"

附录:MySQL 运维 QA 速查

Q1:数据库连接数满了怎么办?

-- 1. 用 root 账号登录
mysql -u root -p

-- 2. 查看当前连接分布
SELECT user, COUNT(*) AS cnt FROM information_schema.PROCESSLIST GROUP BY user;

-- 3. 临时加大连接数
SET GLOBAL max_connections = 500;

-- 4. 杀掉空闲连接(注意:不要 kill 重要业务连接)
-- 杀掉 Sleep 超过 300 秒的连接
SELECT CONCAT('KILL ', id, ';') AS kill_cmd
FROM information_schema.PROCESSLIST
WHERE command = 'Sleep' AND time > 300;
-- 把输出的 KILL 语句复制执行

Q2:磁盘空间满了,MySQL 起不来?

# 1. 清理慢查询日志 / 错误日志
truncate -s 0 /var/log/mysql/slow.log
truncate -s 0 /var/log/mysql/error.log

# 2. 清理 binlog(确认从库已同步后)
mysql -u root -p -e "PURGE BINARY LOGS BEFORE NOW() - INTERVAL 7 DAY;"

# 3. 如果还不行,清理大表历史数据
# 见 DELETE 分批删除章节

Q3:主从同步延迟了怎么办?

-- 1. 查看延迟
SHOW SLAVE STATUS\G
-- 看 Seconds_Behind_Master 字段

-- 2. 常见原因:
--    - 从库写入慢 → 升级从库配置或优化慢查询
--    - 大事务 → 避免长事务,大操作分批执行
--    - 从库在做备份 → 错开备份时间

-- 3. 临时跳过错误(不推荐,仅紧急恢复用)
STOP SLAVE;
SET GLOBAL sql_slave_skip_counter = 1;
START SLAVE;

最后更新: 2026-06-23

📄 已保存 → mysql-document/MySQL-常用操作-001.md

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区