Skip to content

05.项目五 MySQL数据库安全基础

🎯 本项目学习目标

  • 能在 Ubuntu 24.04 LTS 环境下完成 MySQL 8.0 的安装、启动与基础验证
  • 能理解 Ubuntu 默认认证方式,并完成 root 初始登录与基础安全加固
  • 能基于内网场景创建受限账号,并配置远程访问权限
  • 能按最小权限原则完成账号授权、查询、撤销与删除
  • 能识别错误日志、慢查询日志、通用查询日志和 binlog 的用途,并完成 binlog 开启与基础恢复思路演练

第 1 课 安装与验证:让 MySQL “能跑起来”

Section titled “第 1 课 安装与验证:让 MySQL “能跑起来””

把 MySQL 在 Ubuntu 24.04 上装起来,并能验证:服务在跑、能登录、能看到版本与数据库列表

  • mysql --version 输出
  • sudo systemctl status mysql --no-pager 显示 active(running)
  • MySQL 内执行成功:
    • SELECT VERSION();
    • SHOW DATABASES;

MySQL 是世界上最流行的开源关系型数据库管理系统(RDBMS),大量 Web 系统使用它作为核心数据存储。安全运维从安装开始:安装 ≠ 安全,安装只是第一步

特性说明
默认认证插件caching_sha2_password(取代旧版 mysql_native_password
默认字符集utf8mb4(支持 Emoji 等四字节字符)
窗口函数ROW_NUMBER()RANK() 等分析函数
CTE(公共表表达式)WITH ... AS 语法,提升复杂查询可读性
角色管理CREATE ROLE / GRANT role TO user,简化批量权限管理
数据字典元数据存储在 InnoDB 表中,不再依赖 .frm 文件

1.4 安装前准备:拍快照 + 换源(Ubuntu 24.04)

Section titled “1.4 安装前准备:拍快照 + 换源(Ubuntu 24.04)”

第 1 步:拍摄 VMware 快照(安全网)

Section titled “第 1 步:拍摄 VMware 快照(安全网)”
  • Ubuntu 24.04 LTS 换源(DEB822 格式)

    Ubuntu 24.04 使用 /etc/apt/sources.list.d/ubuntu.sources(DEB822 格式)。

    Terminal window
    # 1) 备份原文件
    sudo cp /etc/apt/sources.list.d/ubuntu.sources /etc/apt/sources.list.d/ubuntu.sources.bak
    # 2) 编辑配置文件
    sudo nano /etc/apt/sources.list.d/ubuntu.sources

    将文件内容全部替换为以下内容(阿里云镜像示例):

    Types: deb
    URIs: https://mirrors.aliyun.com/ubuntu
    Suites: noble noble-updates noble-backports
    Components: main restricted universe multiverse
    Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
    Types: deb
    URIs: https://mirrors.aliyun.com/ubuntu
    Suites: noble-security
    Components: main restricted universe multiverse
    Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg

    保存退出后更新索引:

    Terminal window
    sudo apt update
  • Ubuntu 22.04 LTS 换源(传统 sources.list 格式)

    Ubuntu 22.04 使用传统的 /etc/apt/sources.list 文件。

    Terminal window
    # 1) 备份原文件
    sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
    # 2) 编辑配置文件
    sudo nano /etc/apt/sources.list

    将文件内容全部替换为以下内容(阿里云镜像示例):

    deb https://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
    deb https://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
    deb https://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse
    deb https://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse

    保存退出后更新索引:

    Terminal window
    sudo apt update

MySQL 安全运维会频繁涉及日志时间、远程连接和软件源访问。安装前建议先确认系统时间和网络状态:

Terminal window
# 查看系统时间与时区
timedatectl
# 若时区不是 Asia/Shanghai,可设置为上海时区
sudo timedatectl set-timezone Asia/Shanghai
# 测试网络连通性
ping -c 4 mirrors.aliyun.com
Terminal window
# 0) 更新索引
sudo apt update
# 1) 安装 MySQL Server
sudo apt install -y mysql-server
# 2) 启动并设置开机自启
sudo systemctl enable --now mysql
# 3) 验证版本与服务状态
mysql --version
sudo systemctl status mysql --no-pager
Terminal window
sudo mysql_secure_installation
Terminal window
# 方式一:如果刚才已将 root 改为密码认证,用密码登录
mysql -u root -p123456
# 方式二:如果还没改认证插件,用 sudo 登录
sudo mysql
SELECT VERSION();
SHOW DATABASES;
exit;

第 2 课 配置文件与安全基线:把”边界”和”默认坑”补齐

Section titled “第 2 课 配置文件与安全基线:把”边界”和”默认坑”补齐”

我们已经让 MySQL 能跑起来。下一步要让它在正确的边界内运行:远程访问是否允许、字符集是否正确、时间是否一致。否则会出现:远程连不上、乱码、时间错位等常见问题。

  • 找到 Ubuntu 24.04 的主配置文件位置
  • 完成三件”必改且可验证”的基线配置:bind-address / utf8mb4 / time_zone
  • 修改后能重启并验证变量生效

MySQL 配置文件采用 INI 格式。Ubuntu 24.04 的配置文件分散在多个路径,按优先级从高到低排列:

路径说明建议
/etc/mysql/my.cnf全局入口,通常只做 !includedir不要动
/etc/mysql/mysql.conf.d/mysqld.cnf服务端主配置主要编辑这个文件
/etc/mysql/conf.d/自定义补充配置(.cnf 后缀)可新建文件做模块化配置
~/.my.cnf用户级客户端配置可选,简化个人操作

配置文件中有两种配置段:

[mysqld] # 服务端配置(mysqld 进程读取)
[client] # 客户端配置(mysql、mysqldump 等工具读取)

MySQL 默认只监听 127.0.0.1(本机回环),意味着其他机器无法连接。

[mysqld]
bind-address = 0.0.0.0
port = 3306
含义安全性适用场景
127.0.0.1仅本机可连接最高单机开发、不需要远程访问
0.0.0.0监听所有网卡需配合防火墙内网实验、生产环境
具体内网 IP只监听指定网卡中等多网卡服务器、精细化控制

B. 字符集:utf8mb4(避免乱码 / Emoji 问题)

Section titled “B. 字符集:utf8mb4(避免乱码 / Emoji 问题)”
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
[client]
default-character-set = utf8mb4

C. 时区:+08:00(避免时间错 8 小时)

Section titled “C. 时区:+08:00(避免时间错 8 小时)”
[mysqld]
default-time-zone = '+08:00'

修改配置后必须重启服务才能生效:

Terminal window
sudo systemctl restart mysql
sudo systemctl status mysql --no-pager

SHOW VARIABLES 逐项验证配置是否生效:

-- 验证绑定地址
SHOW VARIABLES LIKE 'bind_address';
-- 验证字符集
SHOW VARIABLES LIKE 'character_set_server';
SHOW VARIABLES LIKE 'collation_server';
-- 验证时区
SHOW VARIABLES LIKE 'time_zone';
SELECT NOW();

第 3 课 账号与权限:远程能连,但不用 root(最小权限)

Section titled “第 3 课 账号与权限:远程能连,但不用 root(最小权限)”

我们已经允许 MySQL 监听内网(bind-address),接下来要让宿主机的 Navicat 能连接,同时遵循数据库安全核心原则:不用 root,按最小权限创建业务账号

  • 理解 MySQL 账户识别方式:'用户名'@'主机'
  • 理解权限系统的四层结构(全局 → 数据库 → 表 → 列)
  • 创建一个只允许内网网段连接的用户
  • 给用户授予某个数据库范围内的最小权限
  • 从宿主机 Navicat 远程登录验证
  • 掌握权限变更的生效机制,知道什么时候需要 FLUSH PRIVILEGES
  • 掌握账号生命周期管理(查看、修改、撤销、删除)

在 MySQL 中,账号不是单独的用户名,而是 用户名 + 来源主机 的组合:

'user'@'host'

例如:

写法含义安全建议
'app'@'localhost'只允许本机连接适合本机脚本或维护任务
'app'@'192.168.100.1'只允许宿主机这一台机器连接最精确,推荐用于固定客户端
'app'@'192.168.100.%'允许 192.168.100.x 网段连接适合课堂内网实验
'app'@'%'允许任何来源连接风险高,生产环境避免使用

同一个用户名搭配不同主机,会被视为不同的账号。例如 'app'@'%''app'@'192.168.100.%' 是两条独立账号记录,密码和权限也可以不同。

3.4 MySQL 权限体系:再看”能做什么”

Section titled “3.4 MySQL 权限体系:再看”能做什么””

MySQL 的权限不是”一个用户一个开关”,而是分层逐级检查的:

第一层:连接验证
→ 用户名 + 主机名 是否存在?密码对不对?
→ 失败直接拒绝,不进入下一层
第二层:全局权限(mysql.user 表)
→ GRANT ALL ON *.*
→ 超级管理员才给,普通业务账号跳过
第三层:数据库级权限(mysql.db 表)
→ GRANT SELECT ON mydb.*
→ 大多数业务账号授在这一层
第四层:表级 / 列级权限(mysql.tables_priv / mysql.columns_priv)
→ GRANT SELECT ON mydb.orders
→ 更精细的访问控制

权限授权时通常按全局 → 数据库 → 表 → 列逐步收窄;执行某条 SQL 时,MySQL 会综合匹配这些层级中的权限,只要没有找到可用授权就会拒绝。

分类权限说明常见场景
数据操作SELECT读取数据只读报表账号
INSERT插入数据应用写入
UPDATE修改数据应用更新
DELETE删除数据应用删除
结构管理CREATE创建数据库/表开发者
ALTER修改表结构开发者
DROP删除数据库/表生产环境慎给
INDEX创建/删除索引性能优化
管理权限GRANT OPTION允许该用户授权给别人仅限管理员
SUPER管理服务器级操作仅限 DBA
PROCESS查看所有连接排查问题
FILE读写服务器文件高危,不建议给

3.5 远程连接三要素(排错顺序)

Section titled “3.5 远程连接三要素(排错顺序)”

3.6 创建数据库与远程账号(完整流程)

Section titled “3.6 创建数据库与远程账号(完整流程)”

下面以数据库 stusta 为例,账号只允许 192.168.100.% 网段连接(覆盖宿主机所在网段)。

在虚拟机中登录 MySQL:

Terminal window
sudo mysql
-- 0) 降低密码策略(课堂环境必须,否则 123456 无法通过 MEDIUM 策略)
-- 先检查密码验证组件是否已安装
SELECT COMPONENT_ID, COMPONENT_URN FROM mysql.component;
-- 如果没有 component_validate_password 的记录,才执行下面这行安装:
--INSTALL COMPONENT 'file://component_validate_password';
--SET GLOBAL validate_password.policy = LOW;
--SET GLOBAL validate_password.length = 6;
-- 1) 准备示例库和示例表
CREATE DATABASE IF NOT EXISTS stusta;
USE stusta;
CREATE TABLE IF NOT EXISTS students (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
score DECIMAL(5,2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO students (name, score) VALUES
('张三', 88.5),
('李四', 92.0),
('王五', 76.0);
-- 2) 创建远程账号(网段限制,使用 mysql_native_password 确保 Navicat 兼容)
CREATE USER IF NOT EXISTS 'app'@'192.168.100.%' IDENTIFIED WITH mysql_native_password BY '123456';
-- 3) 最小权限授权(只对 stusta.* 的 CRUD)
GRANT SELECT, INSERT, UPDATE, DELETE ON stusta.* TO 'app'@'192.168.100.%';
-- 4) 验证权限
SHOW GRANTS FOR 'app'@'192.168.100.%';

3.7 防火墙(内网实验环境建议)

Section titled “3.7 防火墙(内网实验环境建议)”

若启用了 ufw,建议只允许宿主机或内网网段访问 3306,而不是向所有来源开放:

Terminal window
# 只允许宿主机访问 MySQL(更推荐,把 IP 替换为你宿主机的实际 IP)
sudo ufw allow from 192.168.100.1 to any port 3306 proto tcp
# 或允许整个实验网段访问
sudo ufw allow from 192.168.100.0/24 to any port 3306 proto tcp
sudo ufw status

3.8 从宿主机 Navicat 远程登录验证

Section titled “3.8 从宿主机 Navicat 远程登录验证”

在宿主机打开 Navicat,新建 MySQL 连接,填写以下信息:

配置项填写内容说明
连接名MySQL-Lab(自定义)仅用于区分连接
主机192.168.100.20虚拟机的实际 IP
端口3306MySQL 默认端口
用户名app刚才创建的最小权限账号
密码123456课堂统一密码

点击”测试连接”,成功后保存并双击打开连接。

连接成功后,在 Navicat 中执行以下验证:

① 确认能看到授权的数据库

在左侧数据库列表中,应该能看到 stusta,但不应该看到 mysql等系统库。

② 验证查询操作(应成功)

双击 stusta 数据库 → 双击 students 表 → 可以查看数据。

也可以在 Navicat 的查询窗口中执行 SQL:

-- 确认当前身份
SELECT USER(), CURRENT_USER();
-- 验证查询
SELECT * FROM stusta.students;
-- 验证写入
INSERT INTO stusta.students (name, score) VALUES ('赵六', 85.0);

③ 验证权限边界(应该报错)

在 Navicat 查询窗口中尝试越权操作:

-- 以下操作都应该失败
CREATE DATABASE hackdb; -- ERROR 1044: 权限不足
DROP TABLE stusta.students; -- ERROR 1142: 权限不足
SELECT * FROM mysql.user; -- ERROR 1142: 权限不足

3.9 权限变更何时生效:FLUSH PRIVILEGES 要不要执行?

Section titled “3.9 权限变更何时生效:FLUSH PRIVILEGES 要不要执行?”

很多同学在修改账号权限后会习惯性执行:

FLUSH PRIVILEGES;

这条语句的作用是:让 MySQL 重新加载权限表,把磁盘中的授权表重新读入内存。但并不是所有权限修改都需要它。

不需要 FLUSH PRIVILEGES 的情况(推荐做法)

Section titled “不需要 FLUSH PRIVILEGES 的情况(推荐做法)”

只要使用 MySQL 官方账号权限语句,权限会自动刷新并立即生效:

CREATE USER 'app'@'192.168.100.%' IDENTIFIED BY '123456';
GRANT SELECT ON stusta.* TO 'app'@'192.168.100.%';
REVOKE SELECT ON stusta.* FROM 'app'@'192.168.100.%';
ALTER USER 'app'@'192.168.100.%' IDENTIFIED BY '123456';
DROP USER 'app'@'192.168.100.%';

这些语句执行后,MySQL 会自动更新内存中的权限缓存,所以不用再手动刷新

需要 FLUSH PRIVILEGES 的情况(不推荐新手这样做)

Section titled “需要 FLUSH PRIVILEGES 的情况(不推荐新手这样做)”

如果直接修改了系统权限表,例如:

UPDATE mysql.user SET Host = '192.168.100.%' WHERE User = 'app';
DELETE FROM mysql.user WHERE User = 'olduser';
INSERT INTO mysql.user (...);

这类操作绕过了 CREATE USER / GRANT / ALTER USER 等标准语句。MySQL 可能还在使用旧的内存权限缓存,此时才需要:

FLUSH PRIVILEGES;
操作方式是否需要 FLUSH PRIVILEGES原因
CREATE USER / ALTER USER / DROP USER不需要MySQL 自动刷新权限缓存
GRANT / REVOKE不需要MySQL 自动刷新权限缓存
UPDATE mysql.user / DELETE FROM mysql.user需要直接改系统表,需手动让 MySQL 重新加载
mysql_secure_installation 最后选择刷新权限表通常选择 Yes向导可能修改系统权限表,刷新可确保生效

账号不是创建完就不管了。安全运维要求对账号进行完整生命周期管理:

-- 查看所有账号
SELECT user, host, plugin, account_locked FROM mysql.user;
-- 只看有密码的账号(排除 auth_socket)
SELECT user, host, plugin FROM mysql.user WHERE plugin != 'auth_socket';
-- 修改其他用户的密码(推荐方式,MySQL 8.0)
ALTER USER 'app'@'192.168.100.%' IDENTIFIED BY '123456';
-- 修改自己的密码
ALTER USER USER() IDENTIFIED BY '123456';
-- 撤销 DELETE 权限(只保留 SELECT, INSERT, UPDATE)
REVOKE DELETE ON stusta.* FROM 'app'@'192.168.100.%';
-- 验证
SHOW GRANTS FOR 'app'@'192.168.100.%';
-- 临时锁定账号(禁止登录,但不删除)
ALTER USER 'app'@'192.168.100.%' ACCOUNT LOCK;
-- 解锁
ALTER USER 'app'@'192.168.100.%' ACCOUNT UNLOCK;
-- 删除账号(不可逆,先确认没有业务在使用)
DROP USER 'app'@'192.168.100.%';

MySQL 8.0 内置了密码验证组件 validate_password,可以强制密码复杂度。

-- 查看当前策略
SHOW VARIABLES LIKE 'validate_password%';
参数含义默认值课堂值生产建议值
validate_password.policy策略等级MEDIUMLOWMEDIUM
validate_password.length密码最短长度868+
validate_password.mixed_case_count大小写字母最少各几个10(LOW 模式不检查)1
validate_password.number_count数字最少几个10(LOW 模式不检查)1
validate_password.special_char_count特殊字符最少几个10(LOW 模式不检查)1

策略等级说明:

  • LOW:只检查长度(课堂环境使用)
  • MEDIUM:长度 + 大小写 + 数字 + 特殊字符(生产环境推荐)
  • STRONG:MEDIUM + 字典检查(高安全要求场景)

第 4 课 日志:会排错、会定位慢、会恢复(重点:binlog)

Section titled “第 4 课 日志:会排错、会定位慢、会恢复(重点:binlog)”

我们已经实现”远程可用 + 最小权限”。最后要补上安全运维闭环:出了问题能查、误操作能追溯、必要时能恢复——这就靠日志。

  • 知道四类日志分别在什么场景用
  • 能查看错误日志、能开启/查看慢查询日志
  • 能开启 binlog,并理解”全量备份 + binlog 回放”的时间点恢复(PITR)思路

4.3 四种主要日志(先会选,再会做)

Section titled “4.3 四种主要日志(先会选,再会做)”
日志类型记录内容主要用途性能影响建议
错误日志(Error Log)启动/关闭/运行错误故障排查第一入口默认开启,必须会看
通用查询日志(General Log)所有 SQL 语句审计/调试仅排错时临时开启
二进制日志(binlog)所有写操作事件复制 + PITR 恢复低-中建议开启(重点)
慢查询日志(Slow Log)超过阈值的 SQL性能定位建议开启(会用即可)

4.4 错误日志:服务异常先看这里

Section titled “4.4 错误日志:服务异常先看这里”

错误日志是排查 MySQL 故障的第一入口。MySQL 启动失败、崩溃、权限错误等都会记录在这里。

Terminal window
# 查看最近 100 行错误日志
tail -100 /var/log/mysql/error.log
# 实时跟踪(排查启动问题时特别有用)
sudo tail -f /var/log/mysql/error.log
-- 在 MySQL 内查看错误日志路径
SHOW VARIABLES LIKE 'log_error';
日志关键字含义排查方向
[ERROR] Can't start server服务启动失败检查端口冲突、配置文件语法、磁盘空间
[ERROR] Access denied认证失败检查用户名/密码/主机权限
[ERROR] Disk full磁盘满清理日志、扩展磁盘
[Warning] IP address 'x.x.x.x' could not be resolvedDNS 反解失败设置 skip_name_resolve

4.5 慢查询日志:系统慢了怎么找 SQL

Section titled “4.5 慢查询日志:系统慢了怎么找 SQL”

慢查询日志记录执行时间超过阈值的 SQL 语句,是定位”系统为什么慢”的关键工具。

-- 查看当前状态
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
-- 开启慢查询日志(临时生效,重启后失效)
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 1; -- 超过 1 秒的 SQL 记录到慢查询日志
SET GLOBAL log_queries_not_using_indexes = 1; -- 没用索引的也记录
-- 在配置文件中永久生效
-- 编辑 /etc/mysql/mysql.conf.d/mysqld.cnf
-- [mysqld] 段添加:
-- slow_query_log = 1
-- slow_query_log_file = /var/log/mysql/slow.log
-- long_query_time = 1
-- log_queries_not_using_indexes = 1
Terminal window
# 查看慢查询日志
sudo tail -50 /var/log/mysql/slow.log
-- 统计慢查询数量
SHOW GLOBAL STATUS LIKE 'Slow_queries';

MySQL 自带了慢查询日志分析工具:

Terminal window
# 按执行次数排序,显示前 10 条
sudo mysqldumpslow -s c -t 10 /var/log/mysql/slow.log
# 按平均执行时间排序
sudo mysqldumpslow -s at -t 10 /var/log/mysql/slow.log
# 按锁定时间排序
sudo mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
参数含义
-s c按查询次数(count)排序
-s at按平均查询时间(avg time)排序
-s t按总锁定时间排序
-t N只显示前 N 条

4.6 通用查询日志:临时排错利器

Section titled “4.6 通用查询日志:临时排错利器”

通用查询日志记录每一条到达 MySQL 的 SQL,包括连接、断开、查询。由于日志量巨大,只在排错时临时开启

-- 查看当前状态
SHOW VARIABLES LIKE 'general_log%';
-- 临时开启(排错时使用)
SET GLOBAL general_log = 1;
SET GLOBAL general_log_file = '/var/log/mysql/general.log';
-- ... 执行需要排查的操作 ...
-- 排查完毕,立即关闭
SET GLOBAL general_log = 0;

4.7 Binlog(重点):开启 + 验证 + PITR 思路

Section titled “4.7 Binlog(重点):开启 + 验证 + PITR 思路”

binlog 是 MySQL 最重要的日志之一。它记录了所有修改数据的操作(INSERT / UPDATE / DELETE / DDL),用途有两个:

  1. 主从复制:从服务器通过读取主服务器的 binlog 来同步数据(下一章会用到)
  2. 数据恢复:配合全量备份实现时间点恢复(PITR)

编辑配置文件:

Terminal window
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

[mysqld] 段添加(或确认存在):

[mysqld]
log_bin = /var/lib/mysql/mysql-bin
binlog_format = ROW
server_id = 1
binlog_expire_logs_seconds = 604800
max_binlog_size = 100M
参数含义建议值
log_binbinlog 文件路径前缀保持默认即可
binlog_format记录格式ROW(推荐,最安全)
server_id服务器唯一标识单机可随意,主从必须不同
binlog_expire_logs_seconds自动清理时间,单位秒604800(7 天)- 2592000(30 天)
max_binlog_size单个 binlog 文件最大体积100M-256M

重启并验证:

Terminal window
sudo systemctl restart mysql
-- 验证 binlog 是否开启
SHOW VARIABLES LIKE 'log_bin'; -- 应该是 ON
-- 查看 binlog 格式
SHOW VARIABLES LIKE 'binlog_format'; -- 应该是 ROW
-- 查看所有 binlog 文件
SHOW BINARY LOGS;
-- 查看当前正在写入的 binlog 文件及位置
SHOW MASTER STATUS;

binlog 是二进制格式,不能直接用 cat 查看,需要使用 mysqlbinlog 工具:

Terminal window
# 解码查看第一个 binlog 文件的内容
sudo mysqlbinlog --base64-output=DECODE-ROWS -v /var/lib/mysql/mysql-bin.000001 | less

参数说明:

参数作用
--base64-output=DECODE-ROWS将 base64 编码解码为可读格式
-v(或 --verbose显示行变更的详细信息(ROW 格式必需)

按时间范围筛选:

Terminal window
sudo mysqlbinlog \
--start-datetime="2026-04-21 08:00:00" \
--stop-datetime="2026-04-21 10:00:00" \
/var/lib/mysql/mysql-bin.000001 | less

按位置范围筛选(更精确):

Terminal window
sudo mysqlbinlog \
--start-position=154 \
--stop-position=1024 \
/var/lib/mysql/mysql-bin.000001 | less
第 1 步:用 Navicat 做全量备份 + 记录 binlog 位置
Section titled “第 1 步:用 Navicat 做全量备份 + 记录 binlog 位置”

全量备份是 PITR 的起点。我们用宿主机 Navicat 图形化完成备份,同时在虚拟机中记录备份时的 binlog 位置。

1a. 记录当前 binlog 位置(备份前)

在虚拟机 MySQL 中执行,切换到新的 binlog 文件并记下位置:

-- 切换 binlog,让备份后的写操作记录到新文件
FLUSH BINARY LOGS;
-- 记录当前 binlog 文件名和位置(务必记下来!)
SHOW MASTER STATUS;

输出类似:

+------------------+----------+
| File | Position |
+------------------+----------+
| mysql-bin.000002 | 157 |
+------------------+----------+

FilePosition 记录下来(例如记在记事本里),后续恢复时要用。

1b. 在 Navicat 中导出备份

  1. 在宿主机打开 Navicat,连接到虚拟机 MySQL(使用 root 账号或有备份权限的账号)
  2. 右键点击 stusta 数据库 → 转储 SQL 文件结构和数据
  3. 选择保存路径,例如保存为 D:\backup\full_backup_stusta.sql
  4. 等待导出完成
第 2 步:模拟备份后的正常业务 + 误操作
Section titled “第 2 步:模拟备份后的正常业务 + 误操作”
-- 备份之后,业务正常运行,产生了新数据
INSERT INTO students (name, gender, age, major, gpa, enrollment_date, email, phone, address)
VALUES ('新生甲', '', 18, '网络安全', 3.70, '2025-09-01', 'newA@stu.edu', '13800008001', '北京市朝阳区');
INSERT INTO students (name, gender, age, major, gpa, enrollment_date, email, phone, address)
VALUES ('新生乙', '', 19, '软件工程', 3.85, '2025-09-01', 'newB@stu.edu', '13800008002', '上海市徐汇区');
-- ⚠️ 10:00 有人误操作,清空了整张表!
DELETE FROM students;
SELECT COUNT(*) FROM students; -- 结果:0 条
第 3 步:用 Navicat 恢复全量备份
Section titled “第 3 步:用 Navicat 恢复全量备份”

发现误操作后,第一步是恢复全量备份,把数据回到备份时的状态:

  1. 在 Navicat 中右键点击 stusta 数据库 → 运行 SQL 文件
  2. 选择之前导出的备份文件 D:\backup\full_backup_stusta.sql
  3. 点击”开始”,等待执行完成
-- 验证:数据回来了,但缺少备份之后的新数据(新生甲、新生乙)
SELECT COUNT(*) FROM students;
第 4 步:从 binlog 定位误操作的位置
Section titled “第 4 步:从 binlog 定位误操作的位置”

现在需要找到 DELETE FROM students 这条误操作在 binlog 中的确切位置,以便回放时在它之前停下来:

Terminal window
# 查看备份之后的 binlog,找到 DELETE 操作的时间和位置
sudo mysqlbinlog --base64-output=DECODE-ROWS -v \
/var/lib/mysql/mysql-bin.000002 | grep -B 10 "DELETE FROM"

输出中关注 # at 行和时间戳:

# at 1800 ← 事件起始位置
#260421 10:00:15 server id 1 end_log_pos 1856 ← 事件结束位置
### DELETE FROM `stusta`.`students`
  • # at 1800:DELETE 事件的起始位置--stop-position 要用这个值)
  • end_log_pos 1856:事件的结束位置
  • 时间2026-04-21 10:00:15(误操作发生时间)

也可以用 --start-position 从备份点开始查看,更精确地浏览事件:

Terminal window
# 从备份记录的位置 157 开始查看所有事件
sudo mysqlbinlog --base64-output=DECODE-ROWS -v \
--start-position=157 \
/var/lib/mysql/mysql-bin.000002 | less
第 5 步:回放 binlog 到误操作前一刻
Section titled “第 5 步:回放 binlog 到误操作前一刻”

确定了误操作的时间或位置后,回放 binlog 中”备份之后、误操作之前”的所有正常操作:

方式 A:按时间回放(简单但不够精确)

Terminal window
sudo mysqlbinlog \
--start-position=157 \
--stop-datetime="2026-04-21 10:00:14" \
--database=stusta \
/var/lib/mysql/mysql-bin.000002 | mysql -u root

方式 B:按位置回放(精确推荐)

Terminal window
# 假设通过上一步确认 DELETE 事件的起始位置是 1800
sudo mysqlbinlog \
--start-position=157 \
--stop-position=1800 \
--database=stusta \
/var/lib/mysql/mysql-bin.000002 | mysql -u root
参数作用
--start-position=157从备份记录的 binlog 位置开始(跳过备份前的旧事件)
--stop-datetime / --stop-position在误操作之前停止
--database=stusta只回放 stusta 数据库的事件
-- 确认数据完整恢复(包含备份后的正常操作)
SELECT COUNT(*) AS '恢复后学生总数' FROM students;
-- 应包含新生甲、新生乙
SELECT * FROM students WHERE name IN ('新生甲', '新生乙', '张三');
-- 张三的 GPA 应该是 3.90(备份后 UPDATE 的结果)
-- 手动切换到新的 binlog 文件(当前文件写满或手动切换)
FLUSH BINARY LOGS;
-- 手动清理 7 天前的 binlog
PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 7 DAY);
-- 清理指定文件之前的所有 binlog
PURGE BINARY LOGS TO 'mysql-bin.000005';

课时核心能力验收点(可检查)
第 1 课:安装验证装得起来服务 active + SELECT VERSION()
第 2 课:配置基线配得安全bind-address / utf8mb4 / time_zone 生效
第 3 课:账号权限管得住人宿主机 Navicat 能用最小权限账号远程登录,且无权执行越权操作
第 4 课:日志与恢复能追溯/能恢复能查看错误/慢查询;binlog 开启并能解释 PITR 三步走

忘记 root 密码:重置方法(Windows / Linux)

Section titled “忘记 root 密码:重置方法(Windows / Linux)”

方法一:Linux / Ubuntu 重置 root 密码

Section titled “方法一:Linux / Ubuntu 重置 root 密码”

思路:停止 MySQL → 用 --skip-grant-tables 跳过权限验证启动 → 手动改密 → 正常重启服务。

为什么要用 --skip-grant-tables?因为跳过授权表时,validate_password 组件也不会加载,任何密码都能设置成功。如果用 --init-file 方法,遇到 MEDIUM 密码策略会导致 123456 被拒绝,服务器直接退出。

Terminal window
# 1) 停止 MySQL 服务
sudo systemctl stop mysql
# 2) 跳过授权表启动(--skip-networking 禁止远程连接,确保安全)
sudo mysqld --skip-grant-tables --skip-networking --user=mysql &

等待几秒让 MySQL 启动完成,然后无密码登录:

Terminal window
# 3) 无密码连接 MySQL
mysql -u root

在 MySQL 中执行改密(必须先 FLUSH PRIVILEGES 重新加载权限表):

FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';
exit

关闭临时实例并正常启动:

Terminal window
# 4) 关闭临时实例(--skip-grant-tables 下无需密码)
sudo mysqladmin -u root shutdown
# 5) 正常启动 MySQL 服务
sudo systemctl start mysql
sudo systemctl status mysql --no-pager

验证新密码:

Terminal window
mysql -u root -p
# 输入 123456

以下示例以 MySQL 8.0 默认服务名 MySQL80 为例。若服务名不同,可在”服务”管理器中查看真实名称。

  1. 以管理员身份打开 CMD 或 PowerShell,停止 MySQL 服务:
Terminal window
net stop MySQL80
  1. 跳过授权表启动 MySQL(--skip-networking 禁止远程连接,确保安全):
Terminal window
"C:\Program Files\MySQL\MySQL Server 8.0\bin\mysqld.exe" --skip-grant-tables --skip-networking --console

看到 MySQL 启动成功后,新开一个管理员 CMD / PowerShell 窗口,无密码连接并改密:

Terminal window
"C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql.exe" -u root
FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';
exit
  1. 回到第一个窗口按 Ctrl + C 关闭临时 MySQL,然后正常启动服务:
Terminal window
net start MySQL80

验证新密码:

Terminal window
"C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql.exe" -u root -p
# 输入 123456

附录 A:MySQL 与 SQL Server 主要差异(对比表)

Section titled “附录 A:MySQL 与 SQL Server 主要差异(对比表)”
特性MySQLSQL Server
许可证开源(GPL)/ 商业双轨商业(微软授权)
运行平台Linux、Windows、macOS主要 Windows(2017+ 支持 Linux)
主要生态LAMP、Web 应用、云原生企业级 Windows 应用
存储引擎多引擎(InnoDB / MyISAM 等)单一引擎
SQL 方言标准 SQL + MySQL 扩展T-SQL(Transact-SQL)
价格免费(社区版)收费(企业版较贵)

附录 B:安装场景与方法对比(课堂不展开)

Section titled “附录 B:安装场景与方法对比(课堂不展开)”
场景说明推荐安装方式
课程实验 / 个人学习VMware 虚拟机中练习apt 安装(本项目推荐)
Web 项目开发本地或测试服务器apt 安装 或 Docker
生产环境部署正式线上服务apt / 二进制包 + 加固脚本
快速验证 / CI 流水线一次性测试环境Docker 容器(秒级启停)
高性能调优需要自定义编译参数源码编译安装

附录 C:MySQL 安装后的目录结构与系统库(速查)

Section titled “附录 C:MySQL 安装后的目录结构与系统库(速查)”
路径说明使用场景
/usr/bin/客户端工具(mysql、mysqladmin、mysqldump 等)日常操作、备份
/usr/sbin/mysqldMySQL 服务器进程排查进程问题
/var/lib/mysql/数据目录备份、迁移、磁盘空间排查
/var/log/mysql/日志目录(错误日志等)故障排查
/etc/mysql/mysql.conf.d/mysqld.cnf主配置文件修改端口、绑定地址、日志等
数据库说明使用场景
mysql用户账户、权限、插件配置管理用户权限、排查认证问题
information_schema元数据库(只读虚拟库)查询表结构、索引信息
performance_schema性能监控数据性能调优、慢查询分析
sys高级视图快速获取性能摘要

附录 D:密码管理多方式(旧系统兼容,课堂不作为主线)

Section titled “附录 D:密码管理多方式(旧系统兼容,课堂不作为主线)”
  • MySQL 8.0 推荐:ALTER USER ... IDENTIFIED BY ...;
  • SET PASSWORD ... = PASSWORD() 为旧语法(8.0 已弃用 PASSWORD())
  • mysqladmin 适用于脚本化,但注意密码泄露风险(命令历史/进程列表)