先说明一下:标题提到的MySQL 8.0.30在本文写作时(2022年1月)尚未正式发布,目前最新稳定版是8.0.28。本文基于MySQL 8.0的实际使用经验,总结升级和使用过程中的常见坑和实战经验。

MySQL 8.0是MySQL的一个大版本,引入了很多新特性:窗口函数、CTE、JSON增强、原子DDL、新的默认认证插件(cachingsha2password)、隐藏索引、降序索引等。这些新特性很好用,但8.0也有不少兼容性问题和坑。

我用MySQL 8.0有几年了,从5.7升级到8.0,也在8.0上做了很多项目。踩过不少坑,有些坑让我熬夜排查,印象深刻。今天分享这些踩坑经验,包括升级问题、性能问题、兼容性问题、运维问题等,希望能帮大家少踩坑,少熬夜。

一、升级相关的坑

从5.7升级到8.0,是最容易出问题的环节。

1. 认证插件不兼容

MySQL 8.0默认的认证插件从mysqlnativepassword改成了cachingsha2password。这个插件更安全,但很多老客户端(比如旧版本的Navicat、PHP的mysqlnd、一些老的ORM框架)不支持,连接时会报"Authentication plugin 'cachingsha2password' cannot be loaded"的错误。

解决方案:

  • 升级客户端到支持cachingsha2password的版本
  • 或者把用户的认证插件改回mysqlnativepassword:

``sql ALTER USER 'user'@'%' IDENTIFIED WITH mysqlnativepassword BY 'password'; ``

  • 或者在my.cnf里设置defaultauthenticationplugin=mysqlnativepassword(8.0已经废弃这个参数,不推荐)

我第一次升级8.0的时候,就遇到了这个问题。应用连不上数据库,排查了半天才发现是认证插件的问题。升级前一定要检查所有客户端和驱动是否支持新的认证方式。

2. 保留字增加

MySQL 8.0增加了一些保留字,比如ROWS、RECURSIVE、SYSTEM、WINDOW等。如果你的表名或字段名用了这些保留字,升级后会报错。

比如5.7里可以用rows做字段名,8.0里就不行了,必须加反引号。

解决方案:

  • 升级前用mysqlcheck检查有没有保留字冲突
  • 或者给字段名加反引号
  • 最好的方式是改字段名,避免用保留字

3. 数据字典变化

MySQL 8.0把数据字典从MyISAM表改成了InnoDB表,实现了原子DDL。这意味着DDL操作(CREATE TABLE、ALTER TABLE等)要么全部成功,要么全部回滚,不会出现5.7里那种DDL中断后表损坏的情况。

但这也带来了一些问题:

  • 不能直接修改mysql库下的系统表了(比如直接UPDATE mysql.user改密码),必须用SQL语句
  • 一些老的运维脚本(直接操作系统表的)会失效
  • frm文件没了,表结构都存在数据字典里

升级前要检查有没有直接操作系统表的脚本,及时改造。

4. 升级顺序

MySQL 8.0不支持从5.6直接升级,必须先升到5.7再升8.0。而且5.7必须是5.7.9以上版本。

升级方式:

  • 原地升级:停库,替换二进制文件,启动,跑mysql_upgrade(8.0.16后自动升级数据字典)
  • 逻辑导出导入:用mysqldump导出,在8.0上导入
  • 推荐用逻辑导出导入,更安全,虽然慢一点

升级前一定要备份!一定要备份!一定要备份!重要的事情说三遍。

二、SQL和性能相关的坑

1. ONLYFULLGROUP_BY默认开启

MySQL 8.0默认开启了ONLYFULLGROUP_BY模式。这意味着GROUP BY查询中,SELECT里的非聚合列必须出现在GROUP BY里,否则报错。

5.7里这个模式也有,但很多人关掉了。8.0默认开启后,一些老的SQL会报错:

-- 8.0里会报错,因为name不在GROUP BY里
SELECT id, name, COUNT(*) FROM table GROUP BY id;

解决方案:

  • 改SQL,把非聚合列加到GROUP BY里,或者用ANY_VALUE()包裹
  • 或者在my.cnf里关掉ONLYFULLGROUP_BY(不推荐,会影响数据准确性)

我踩过这个坑,升级后一批老的报表SQL报错,连夜改SQL。建议升级前就把所有SQL检查一遍。

2. 隐式类型转换导致索引失效

MySQL 8.0对隐式类型转换的处理更严格了。如果字段是字符串类型,查询时用数字,会导致索引失效。

比如字段phone是varchar类型,查询时写WHERE phone = 13800138000(数字),8.0里会不走索引,全表扫描。5.7里可能还能走索引。

解决方案:

  • 查询时类型要匹配,字符串字段用字符串值:WHERE phone = '13800138000'
  • 或者用CAST显式转换

这个问题很隐蔽,升级后可能发现某些查询突然变慢了,就是隐式转换导致的。

3. 窗口函数的性能坑

窗口函数是8.0的好特性,但用不好也有性能问题。比如ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...),如果PARTITION BY的列基数很大,或者没有合适的索引,会很慢。

我曾经写了一个窗口函数查询,数据量几百万,跑了几十秒。后来加了合适的复合索引,优化到了1秒内。

建议:

  • 窗口函数的PARTITION BY和ORDER BY列要建索引
  • 大数据量下,窗口函数可能不如子查询+临时表快,要实际测试
  • 用EXPLAIN ANALYZE分析执行计划

4. CTE(公用表表达式)的物化问题

8.0支持WITH ... AS (...)的CTE语法。但CTE默认是不物化的,每次引用都会重新计算。如果CTE被多次引用,而且计算复杂,会很慢。

8.0.14以后支持WITH ... AS (... ) /+ MATERIALIZE /来强制物化CTE。或者把CTE改成临时表。

我遇到过一个CTE被引用了3次,每次都重新计算,查询很慢。加了MATERIALIZE提示后,性能提升了好几倍。

三、InnoDB相关的坑

1. 大事务导致undo日志膨胀

MySQL 8.0的undo日志可以放在独立的表空间里,也支持在线收缩。但如果有大事务长时间不提交,会导致undo日志膨胀,占用大量磁盘空间。

我遇到过一次,一个开发同学跑了一个大事务,更新了几百万行数据没提交,undo表空间涨到了几十G,磁盘快满了。最后只能杀掉事务,然后收缩undo表空间。

建议:

  • 避免大事务,分批提交
  • 监控长事务,设置innodbrollbackon_timeout
  • undo表空间单独放,设置自动收缩(innodbundolog_truncate=ON)

2. 自增主键耗尽

很多表用INT自增主键,INT的最大值是21亿。如果表数据增长快,可能会耗尽自增ID,导致插入报错。

8.0里可以用BIGINT做主键,或者用UUID。但UUID有性能问题(乱序导致页分裂)。推荐用BIGINT自增,或者雪花算法生成有序ID。

建议:

  • 建表时主键用BIGINT UNSIGNED,不要用INT
  • 监控自增ID的使用量,快到上限时提前处理

3. 死锁和锁等待

MySQL 8.0的锁机制和5.7基本一致,但有一些细节变化。比如8.0里SELECT ... FOR UPDATE NOWAIT和SKIP LOCKED,用不好可能导致死锁。

建议:

  • 开启innodbprintall_deadlocks,记录死锁日志
  • 业务上尽量按相同顺序访问表,减少死锁
  • 用SKIP LOCKED做任务队列时,要注意事务隔离级别

4. Buffer Pool预热

MySQL重启后,Buffer Pool是空的,刚启动时性能很差,因为所有查询都要从磁盘读数据。8.0支持Buffer Pool自动转储和恢复,重启时自动加载热数据。

配置:

innodb_buffer_pool_dump_at_shutdown=ON
innodb_buffer_pool_load_at_startup=ON

这个功能默认是开启的,但如果重启时没正常关闭(比如kill -9),就不会转储。建议正常关闭数据库,或者手动dump buffer pool。

四、运维相关的坑

1. 慢查询日志格式变化

MySQL 8.0的慢查询日志增加了一些字段,比如logslowextra,可以记录更多信息(用户、主机、线程ID等)。但一些老的慢查询分析工具(比如pt-query-digest的老版本)可能不兼容新格式。

建议升级分析工具,或者关闭logslowextra保持旧格式。

2. 备份工具兼容性

mysqldump在8.0里有一些变化,比如默认开启--column-statistics,老版本的mysqldump不能备份8.0的库。一定要用8.0版本的mysqldump来备份8.0的数据库。

xtrabackup也要用支持8.0的版本(Percona XtraBackup 8.0),老版本不兼容。

3. 主从复制的坑

8.0的主从复制有一些新特性:

  • 默认开启GTID
  • 支持二进制日志事务压缩(binlogtransactioncompression)
  • 支持并行复制(writeset模式)

但升级主从时要注意:

  • 先升从库,再升主库
  • 5.7的从库不能复制8.0的主库(高版本到低版本不支持)
  • 升级后检查复制状态,确保没有延迟和错误

我遇到过一次,主库升了8.0,从库还是5.7,复制直接断了。只能把从库也升了,或者重新搭从库。

4. 密码管理

8.0的密码管理更严格了:

  • 密码有过期策略(defaultpasswordlifetime)
  • 密码历史记录(password_history),不能重复用旧密码
  • 密码强度验证(validate_password组件)

这些安全特性很好,但如果不知道,可能会突然发现密码过期了连不上。建议:

  • 合理设置密码过期时间,或者对应用账号设置不过期
  • 监控密码过期时间,提前修改
  • 应用账号不要设置太严格的密码策略

五、性能优化经验

最后分享一些MySQL 8.0的性能优化经验。

1. 合理使用索引

  • 用EXPLAIN分析查询,确保走索引
  • 复合索引遵循最左前缀原则
  • 不要在索引列上用函数或运算
  • 定期用ANALYZE TABLE更新统计信息
  • 8.0支持隐藏索引(INVISIBLE),可以测试删除索引的影响,不用真的删

2. 优化SQL

  • 避免SELECT *,只查需要的列
  • 分页用延迟关联(先查ID再关联),避免深分页
  • 大表分页用游标(WHERE id > last_id LIMIT 10)
  • 用EXISTS代替IN(大数据量下)
  • 合理使用批量插入,减少事务次数

3. 配置优化

  • innodbbufferpool_size设为物理内存的50%-70%
  • innodbflushlogattrx_commit=2(允许丢1秒数据,性能好)或1(安全,性能差)
  • sync_binlog=1000(批量刷binlog,性能好)或1(安全)
  • max_connections根据实际情况设置,不要太大
  • 临时表和排序缓存合理设置

4. 架构优化

  • 读写分离,主库写从库读
  • 分库分表,大表拆小表
  • 加缓存(Redis),减少数据库压力
  • 用消息队列削峰填谷
  • 定期归档历史数据

六、写在最后

MySQL 8.0是一个很优秀的版本,新特性多,性能好,安全性高。但升级和使用过程中确实有不少坑,需要小心应对。

本文总结了我踩过的一些坑:认证插件不兼容、保留字冲突、ONLYFULLGROUP_BY、隐式类型转换、大事务、主从复制问题等。这些坑很多人都踩过,希望我的经验能帮大家少走弯路。

MySQL 8.0还在持续更新,8.0.30及以后的版本会有更多新特性和bug修复。升级前一定要做好测试,在测试环境验证没问题了再升生产环境。

最后,记住数据库运维的三原则:备份、备份、还是备份。任何操作前都要备份,出了问题才能恢复。

2022年了,MySQL依然是最流行的关系型数据库。掌握MySQL,是每个后端和运维工程师的基本功。希望这篇文章对你有帮助。如果你有其他踩坑经验,欢迎在评论区交流。

祝大家的数据库永远稳定,查询永远飞快,永不宕机。