213 lines
8.7 KiB
Markdown
213 lines
8.7 KiB
Markdown
|
||
|
||

|
||
|
||
> 项目跑了五年的 MySQL 5.7,某天下午决定升到 8.0。迁移脚本顺利跑完,服务重启,然后——连接报错、查询行为异常、某个 GROUP BY 语句直接执行失败。排查到凌晨两点,才把所有问题逐一定位清楚。这些坑本可以提前避开。
|
||
|
||
## **前言**
|
||
|
||
MySQL 5.7 已于 **2023年10月正式 EOL**,不再有安全补丁,继续用的风险只会越来越高。但"要不要升"从来不是问题,问题是"升之前要搞清楚哪些东西会变"。本文把两个版本的核心差异逐一拆开,踩过的坑全写在里面。
|
||
|
||
## **一、默认字符集:从 latin1 到 utf8mb4**
|
||
|
||
这是升级后最容易被忽视、也最容易踩坑的地方。
|
||
|
||
|版本|默认字符集|默认排序规则|
|
||
|---|---|---|
|
||
|MySQL 5.7|latin1|latin1_swedish_ci|
|
||
|MySQL 8.0|utf8mb4|utf8mb4_0900_ai_ci|
|
||
|
||
MySQL 5.7 的默认字符集是 `latin1`,大量中文项目在建库时如果没有手动指定 `utf8mb4`,就会踩表情符号存储失败、中文乱码等问题。8.0 默认就是 `utf8mb4`,这个历史遗留坑算是填上了。
|
||
|
||
但要注意:`utf8mb4_0900_ai_ci` 的排序规则和 5.7 常用的 `utf8mb4_general_ci` **行为不完全一致**,迁移时如果直接 dump/restore,可能导致排序结果、索引行为出现细微差异。建表时建议显式指定,别依赖默认值。
|
||
|
||
## **二、认证插件变了,连接可能直接报错**
|
||
|
||
这是 8.0 升级后反馈最多的"坑"之一。
|
||
|
||
MySQL 8.0 将默认认证插件从 `mysql_native_password` 改为 `caching_sha2_password`。
|
||
|
||
```text
|
||
-- 查看当前用户的认证插件
|
||
SELECT user, host, plugin FROM mysql.user;
|
||
```
|
||
|
||
旧版驱动(比如某些 PHP、Java 早期 JDBC 驱动、Navicat 老版本)不支持 `caching_sha2_password`,连接时会直接报错:
|
||
|
||
```text
|
||
Authentication plugin 'caching_sha2_password' cannot be loaded
|
||
```
|
||
|
||
**临时解法**是把特定账户改回旧插件:
|
||
|
||
```text
|
||
ALTER USER 'your_user'@'%' IDENTIFIED WITH mysql_native_password BY 'your_password';
|
||
```
|
||
|
||
但长期来看应该升级驱动。安全性上,`caching_sha2_password` 比 `mysql_native_password` 更强,是正确方向。
|
||
|
||
> ⚠️ 注意:MySQL 8.4 开始已彻底废弃 `mysql_native_password`,8.0 用户如果在过渡期,要提前把驱动升到位。
|
||
|
||
## **三、SQL 能力大幅增强**
|
||
|
||
这是 8.0 最值得升级的理由之一,尤其对写复杂查询的开发来说。
|
||
|
||
### **3.1 窗口函数(Window Functions)**
|
||
|
||
5.7 没有窗口函数,要实现排名、滑动求和这类需求,只能靠变量模拟,写法丑且性能差。8.0 原生支持:
|
||
|
||
```text
|
||
-- 按部门分组,对每个员工计算薪资排名
|
||
SELECT
|
||
name,
|
||
dept,
|
||
salary,
|
||
RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS salary_rank
|
||
FROM employees;
|
||
```
|
||
|
||
类似的还有 `ROW_NUMBER()`、`DENSE_RANK()`、`LAG()`、`LEAD()`、`SUM() OVER()` 等,这些在数据分析、报表场景里极其常用。
|
||
|
||
### **3.2 公共表表达式(CTE)**
|
||
|
||
支持 `WITH` 语法,递归查询终于可以写了:
|
||
|
||
```text
|
||
-- 递归查询组织架构
|
||
WITH RECURSIVE org AS (
|
||
SELECT id, name, parent_id, 1 AS depth
|
||
FROM department WHERE parent_id IS NULL
|
||
UNION ALL
|
||
SELECT d.id, d.name, d.parent_id, o.depth + 1
|
||
FROM department d
|
||
JOIN org o ON d.parent_id = o.id
|
||
)
|
||
SELECT * FROM org;
|
||
```
|
||
|
||
5.7 要实现同样逻辑,要么存储过程,要么应用层递归,代码量和维护成本都更高。
|
||
|
||
### **3.3 JSON 能力全面增强**
|
||
|
||
5.7 引入了 JSON 类型,但操作函数偏基础。8.0 大幅扩展了 JSON 函数库,新增了:
|
||
|
||
- `JSON_TABLE()`:将 JSON 数组展开成关系表,可直接 JOIN 查询
|
||
- `->>`:简化的 JSON 路径提取操作符
|
||
- 对 JSON 索引的优化支持更完善
|
||
|
||
对于半结构化数据存 MySQL 的场景,8.0 的 JSON 能力明显更成熟。
|
||
|
||
## **四、性能:8.0 整体更强,但有细节**
|
||
|
||
官方 Benchmark 数据显示,MySQL 8.0 在高并发读写场景下比 5.7 有明显提升,尤其是:
|
||
|
||
- **InnoDB 读写锁优化**:减少了 redo log 的锁竞争,高并发写入吞吐量更高
|
||
- **Hash Join**:8.0.18 起支持 Hash Join,某些等值 JOIN 场景性能提升显著
|
||
- **隐式排序消除**:优化器对 GROUP BY 不再默认排序,在不需要有序结果时更快
|
||
|
||
不过也有反例:部分复杂查询在 8.0 上因为优化器策略变化,执行计划反而比 5.7 差。升级前建议用线上慢查询日志做一轮回归测试,重点关注执行计划是否有变化。
|
||
|
||
```text
|
||
-- 对比执行计划
|
||
EXPLAIN FORMAT=JSON SELECT ...;
|
||
```
|
||
|
||
## **五、数据字典:从文件迁移到 InnoDB**
|
||
|
||
5.7 的数据字典部分存在 `.frm` 文件中,部分在 `information_schema` 里,管理分散,数据库文件目录下一堆 `.frm` 文件是常见风景。
|
||
|
||
8.0 将数据字典完全迁移到 InnoDB 系统表,不再有 `.frm` 文件。好处是:
|
||
|
||
- DDL 操作支持**原子性**(Atomic DDL):`CREATE TABLE` 失败不会留下半截文件
|
||
- `information_schema` 查询性能提升
|
||
- 数据库文件目录更干净
|
||
|
||
## **六、不可见索引(Invisible Index)**
|
||
|
||
运维场景里很实用的一个功能。
|
||
|
||
```text
|
||
-- 将索引设为不可见(优化器不会使用,但索引仍然维护)
|
||
ALTER TABLE orders ALTER INDEX idx_status INVISIBLE;
|
||
|
||
-- 验证没有这个索引时的查询性能,没问题再删除
|
||
ALTER TABLE orders DROP INDEX idx_status;
|
||
```
|
||
|
||
5.7 想验证"删掉某个索引会不会影响查询",只能真删,发现有问题再重建,代价很高。8.0 可以先设置为不可见,观察一段时间没问题再正式删除,给了一个反悔的机会。
|
||
|
||
## **七、角色管理(Role-Based Access Control)**
|
||
|
||
5.7 的权限管理是直接给用户 GRANT,账号一多就乱。8.0 引入了 Role:
|
||
|
||
```text
|
||
-- 创建角色并授权
|
||
CREATE ROLE 'app_read';
|
||
GRANT SELECT ON mydb.* TO 'app_read';
|
||
|
||
-- 把角色赋给用户
|
||
GRANT 'app_read' TO 'alice'@'%';
|
||
```
|
||
|
||
对于多人协作、权限分层管理的团队来说,这个功能能省不少重复操作。
|
||
|
||
## **八、两个版本的主要差异汇总**
|
||
|
||
|维度|MySQL 5.7|MySQL 8.0|
|
||
|---|---|---|
|
||
|官方维护状态|❌ 已停止(2023.10 EOL)|✅ 活跃维护中|
|
||
|默认字符集|latin1|utf8mb4|
|
||
|默认认证插件|mysql_native_password|caching_sha2_password|
|
||
|窗口函数|❌ 不支持|✅ 支持|
|
||
|CTE / 递归查询|❌ 不支持|✅ 支持|
|
||
|Hash Join|❌ 不支持|✅ 支持(8.0.18+)|
|
||
|原子 DDL|❌|✅|
|
||
|不可见索引|❌|✅|
|
||
|RBAC 角色管理|❌|✅|
|
||
|数据字典存储|.frm 文件 + InnoDB|全部 InnoDB|
|
||
|JSON 能力|基础|增强(JSON_TABLE 等)|
|
||
|高并发写入性能|基准|整体更优|
|
||
|
||
## **九、怎么选?**
|
||
|
||
### **新项目:直接上 8.0**
|
||
|
||
没有历史包袱,没有理由选 5.7。8.0 的 SQL 能力、性能、安全性全面领先,且是官方当前维护的版本。选稳定的 GA 版本(比如 8.0.36+),不要追最新的 8.4 或 9.x,坑还没被社区踩完。
|
||
|
||
### **存量项目:评估后计划迁移**
|
||
|
||
核心建议是**不要拖**,5.7 已经没有安全补丁了,跑在生产上的风险会越来越高。
|
||
|
||
迁移步骤建议:
|
||
|
||
```text
|
||
1. 升级驱动和 ORM 版本,确认兼容 caching_sha2_password
|
||
2. 在测试环境搭 8.0,导入生产数据跑回归测试
|
||
3. 重点检查:字符集、排序规则、执行计划变化、触发器/存储过程
|
||
4. 用 pt-upgrade 或 MySQL Shell 的升级检查工具做兼容性扫描
|
||
5. 灰度切流,监控错误日志和慢查询
|
||
```
|
||
|
||
### **短期无法迁移的存量项目**
|
||
|
||
如果因为各种原因(比如旧系统改不动、第三方软件限制)暂时无法升级:
|
||
|
||
- 确保数据库不直接暴露在公网
|
||
- 定期审计账号权限,关掉不用的账号
|
||
- 把升级计划排进 roadmap,设一个明确的截止时间
|
||
|
||
## **十、常见升级踩坑速查**
|
||
|
||
|问题|原因|解决方案|
|
||
|---|---|---|
|
||
|连接报认证错误|驱动不支持 caching_sha2_password|升级驱动 或 改用 mysql_native_password|
|
||
|查询结果排序变了|排序规则从 general_ci 变为 0900_ai_ci|建表时显式指定排序规则|
|
||
|GROUP BY 报错|8.0 对非聚合字段的检查更严格|修复 SQL,显式使用聚合函数或 ANY_VALUE()|
|
||
|存储过程/函数行为异常|SQL mode 默认值变化|检查 sql_mode,按需调整|
|
||
|执行计划变差|优化器策略调整|用 HINT 或 optimizer_switch 强制执行计划|
|
||
|
||
## **总结**
|
||
|
||
MySQL 8.0 不只是版本号的升级,从 SQL 能力到底层架构都有实质性改进。5.7 的 EOL 也意味着继续使用的技术债会越来越重。
|
||
|
||
对于新项目,8.0 是唯一合理的选择。对于存量项目,建议把迁移提上日程,越早处理,成本越低。 |