
前几天给一台 Windows 10 服务器做 MySQL 5.5 到 5.7 的升级过程虽然不算惊险但出的问题一个接一个服务起不来、导入到一半报 key too long、跑了几年的 SQL 到新库直接语法错误……整个过程整理一下给同样被老库绑住手脚的人做个参考。MySQL 5.5 到现在是真的老了很多新特性没有、性能瓶颈明显而且官方早就停止维护安全上完全裸奔5.7 则是 5.x 系列里最成熟的版本从 5.5 迁过去代码改动通常又不大属于性价比相当高的升级路径。这篇文章就围绕 Windows 10 环境下的 MySQL 5.5 - 5.7 完整升级来写覆盖方案选型、事前备份、安装配置、数据迁移、升级后验证和常见问题适合维护老系统的 DBA也适合兼职管数据库的后端开发。1. 升级前先把方案想清楚1.1 就地升级与逻辑迁移怎么选MySQL 跨大版本升级业界最常见的是两条路线。第一条是就地升级in-place upgrade听起来很省事把新版本的二进制文件或安装包覆盖到原目录数据文件原地不动启动新版后跑mysql_upgrade完成系统表和字典的升级。但这里有个很现实的问题MySQL 官方对 5.5 升 5.7 并不是让你一步跳过去而是要求先升级到 5.6再升级到 5.7。也就是说你要经历两次大版本跳跃每一次都要处理系统表结构变化、权限表字段变化、默认 sql_mode 变化这些事。在 Windows 10 上还要面对服务注册、my.ini 路径、MSI 安装器自动初始化数据目录等一系列干扰过程相当容易翻车。第二条是逻辑迁移用mysqldump把 5.5 里的数据、存储过程、触发器等导出成 SQL 文件导入到新装的 5.7 实例里。这相当于把数据“翻译”了一遍再放进新家旧数据文件完全不参与升级风险天然隔离。我最推荐的就是这条路线尤其适合数据量在几十 GB 以内、停机窗口可接受的场景。逻辑迁移唯一的短板是停机时间取决于数据量但换来的好处是可控性和回滚能力都强得多。1.2 迁移窗口、停机与回滚计划接手一台还在跑 MySQL 5.5 的 Windows 10 服务器第一反应不是动手而是先算账这台机器上有多少个库、总数据量多大、哪些业务在高峰期不能中断、有没有从库或定时备份。我的习惯是把升级分成三个阶段准备阶段、迁移阶段、切换阶段。准备阶段先备份并搭建新实例迁移阶段导入数据并验证切换阶段只做停旧库、改端口、启新库这几个动作。真正的停机时间其实只有切换阶段那几分钟而不是整个导入过程的时长。实际操作上我一般保留旧库服务不卸载让新 5.7 实例先跑在 3307 端口等数据导入完成、应用测试通过后再停掉旧 5.5把新实例改成 3306 端口启动。这样万一新库有问题旧库还在原地随时能恢复。千万别一上来就把旧服务停了、旧数据目录删了否则一旦导入出现意外你连回滚的资格都没有。备份文件建议至少保留两份一份放本机磁盘一份拷贝到其他机器或外置盘。机器上如果开了 Windows 自动更新迁移那几天建议临时暂停避免重启导致迁移中断。2. 升级前检查与备份这一步省了后面全是坑2.1 升级前先搞清楚现状备份之前我建议先把这四件事查清楚。第一版本和基础信息。登录旧库执行SELECT VERSION();确认是 5.5.x 的具体小版本同时看SHOW VARIABLES LIKE character_set_server;和SHOW VARIABLES LIKE collation_server;弄清楚当前默认字符集和排序规则。5.5 时代很多库还在用 latin1 或 utf8后面导入新库时要决定是保持原样还是统一转成 utf8mb4。第二存储引擎分布。用一句 SQL 把库里的引擎统计出来SELECT TABLE_SCHEMA, ENGINE, COUNT(*) AS cnt FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN (mysql,information_schema,performance_schema) GROUP BY TABLE_SCHEMA, ENGINE;重点看有没有 MyISAM 表。MyISAM 不支持事务逻辑备份时无法用--single-transaction拿到一致快照而且 5.7 里虽然还支持 MyISAM但性能和维护都不如 InnoDB。如果业务允许建议升级时顺手把 MyISAM 表转成 InnoDB但要提前评估锁表时间。第三字符集和排序规则。查一下哪些表是 utf8mb4哪些还是 latin1。5.5 已经支持 utf8mb4但很多老库默认建表时还是 utf8。如果数据库里存了 emoji 或生僻字转成 utf8mb4 是有必要的如果只是普通中文保持 utf8 也能用不需要为了升级而折腾一次大规模字符集转换。第四旧 SQL 对默认行为的依赖。5.5 默认sql_mode是空的5.7 默认带了一堆严格模式最典型的是ONLY_FULL_GROUP_BY和NO_ZERO_DATE。老系统里常见的GROUP BY写法、时间字段用0000-00-00 00:00:00做默认值在新版里都可能直接报错。升级前最好把应用里执行频率最高的 SQL 拉出来过一遍也可以先在新实例上导入后抓慢日志和错误日志。2.2 备份不仅备数据还要备权限和对象很多人备份只记着mysqldump导出数据库却容易漏掉存储过程、触发器、事件和用户权限。我的导出命令一般是这样的mysqldump -uroot -p --single-transaction --routines --triggers --events --hex-blob --default-character-setutf8mb4 --databases yourdb D:\backup\yourdb_5_5_backup.sql拆开说几个参数的用途。--single-transaction让 InnoDB 在导出时基于一致快照不锁业务表--routines导出存储过程和函数--triggers导出触发器--events导出事件调度器里的定时任务--hex-blob把二进制内容用十六进制输出避免特殊字符被各种编码问题搞坏--databases让导出文件里包含建库语句和USE语句导入时不用手动切库。如果你混用了 MyISAM 表--single-transaction对 MyISAM 是无效的最好补一个--lock-tablestrue或在业务低峰期执行。另外--default-character-set要和实际数据编码匹配如果旧库是 utf8mb4 就写utf8mb4如果是 latin1 就写latin1否则导出的文件里中文可能已经是乱码。用户权限这块我从来不建议直接导出旧库的mysql.user表导入新库因为 5.5 和 5.7 权限表结构差异很大字段都变了硬导容易出幺蛾子。正确做法是在旧库上跑一句生成授权查看语句SELECT DISTINCT CONCAT(SHOW GRANTS FOR , user, , host, ;) FROM mysql.user;拿到结果后逐条执行SHOW GRANTS把返回的授权语句保存下来导入新库后重新创建用户并执行授权。如果你的业务账号只有两三个直接在新库里手动创建也完全可行。注意 5.7 默认NO_AUTO_CREATE_USER是开启的GRANT语句不再自动创建不存在的用户必须先用CREATE USER建号再授权。2.3 先在测试环境跑一遍别上来就动生产如果条件允许强烈建议先在开发机或同一台机器的 3307 端口新实例上做一次完整导入。这一步的价值不是“练手”而是提前暴露兼容性问题尤其是索引超长、sql_mode 报错、字符集乱码这些老库迁移到新版的典型症状。我这次就是先在 3307 实例上导入结果立刻遇到了 1071 索引过长错误直接在正式切换前就解决了比等到切换窗口里再处理舒服太多。测试的时候建议记录一份导入耗时。比如 10GB 的库在目标服务器上导入花了多久心里有数。正式迁移窗口就需要按这个时间预留再加缓冲别排得太满。同一台机器开双实例还有个好处新库导完后可以停掉旧库让应用临时连到新库做一轮冒烟测试发现不对劲马上切回旧库。3. 安装新版 5.7 与最小化配置3.1 版本选择与 ZIP 安装方式MySQL 5.7 系列最终的社区版是 5.7.44之后官方就不再更新了。所以版本选择很简单直接下载 5.7.44 就可以它是整个 5.7 生命周期里修复问题最多、最稳定的一个版本。下载时选 ZIP Archive不要图省事用 MSI 安装器。MSI 在 Windows 10 上会创建默认数据目录、默认服务还经常把配置写到C:\ProgramData\MySQL升级场景下这种“自动化”反而碍事。ZIP 解压后所有东西都放在自定义目录里服务、配置、数据目录全部手动控制出问题也好排查。假设我解压到D:\mysql57\mysql-5.7.44-winx64数据目录规划为D:\mysql57\data以管理员身份打开 cmd按下面顺序操作mkdir D:\mysql57\data mysqld --defaults-fileD:\mysql57\my.ini --initialize-insecure mysqld --install MySQL57 --defaults-fileD:\mysql57\my.ini net start MySQL57--initialize-insecure会初始化数据目录并生成一个密码为空的 root 用户初始化完成后立刻登录并修改密码mysql -uroot ALTER USER rootlocalhost IDENTIFIED BY 你的新密码;用--initialize-insecure而不是--initialize主要是图省事不用去错误日志里翻临时密码。生产环境请务必在启动后马上改密码。如果初始化时提示目录非空先确认是不是之前残留的旧数据目录千万别让新版本初始化去碰旧数据文件。3.2 my.ini 关键参数怎么定ZIP 方式安装后默认没有 my.ini需要自己在D:\mysql57下手动建一个。下面是我这次用的最小化配置每个参数都值得认真看一眼[mysqld] basedirD:/mysql57/mysql-5.7.44-winx64 datadirD:/mysql57/data port3307 character-set-serverutf8mb4 collation-serverutf8mb4_general_ci lower_case_table_names1 max_allowed_packet256M innodb_buffer_pool_size2G innodb_log_file_size512M innodb_flush_log_at_trx_commit1 innodb_default_row_formatdynamic sql_modeSTRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION explicit_defaults_for_timestampOFF [client] default-character-setutf8mb4port3307是故意的因为旧库 5.5 还占着 3306新库先跑 3307验证完再改。lower_case_table_names1保持 Windows 平台默认的大小写不敏感行为5.5 和 5.7 在 Windows 上默认都是 1没必要改。innodb_default_row_formatdynamic很重要等你在导入时报 1071 索引超长就懂了稍后单独说。innodb_buffer_pool_size建议设成物理内存的 50% 到 70%默认 128M 在导入大库时会让你等到怀疑人生。sql_mode这里我保留了 5.7 的严格模式但如果旧系统 SQL 太野可以先临时改成只有NO_ENGINE_SUBSTITUTION让业务先跑起来再慢慢修 SQL这个取舍后面细说。max_allowed_packet256M也不只是给导入用的。5.5 默认 4M遇到大的 INSERT 或超大 BLOB 很容易报Packet too large。客户端连接时如果走 ODBC 或驱动可能也需要在连接串里设置对应参数。4. 数据导入与对象迁移实操4.1 先建库建账号还是直接导如果你的 dump 文件是用--databases导出的文件里已经带了CREATE DATABASE和USE语句导入时直接执行即可。但这里有个容易踩的坑导入目标环境里如果已经存在同名库mysqldump 生成的建表语句默认带DROP TABLE IF EXISTS会把同名表直接重建等于清空现有数据再导入。所以强烈建议导入进一个干净的新实例或者至少提前确认同名库不是你刚建好的测试库。账号我通常在建库之后、导入业务数据之前先创建。原因是 dump 文件里的存储过程、视图、触发器都带着DEFINER信息如果 definer 指定的账号在新库里不存在导入完成后调用这些对象会报ERROR 1449。稳妥一点的做法是先根据旧库的SHOW GRANTS结果创建好所有需要保留的账号再做数据导入definer 能对得上后面就少很多麻烦。4.2 导入命令与提速技巧Windows 环境下我推荐用 cmd 里的 mysql 客户端执行 source而不是去看 PowerShell 的重定向。PowerShell 默认编码和处理重定向的方式都容易出问题中文数据分分钟变乱码。正确姿势是先登录 mysqlmysql -uroot -p --default-character-setutf8mb4然后进入 mysql 交互界面执行USE yourdb; SET FOREIGN_KEY_CHECKS0; SET UNIQUE_CHECKS0; SET SESSION sql_log_bin0; SOURCE D:/backup/yourdb_5_5_backup.sql; SET FOREIGN_KEY_CHECKS1;FOREIGN_KEY_CHECKS0和UNIQUE_CHECKS0能在导入时省掉大量外键检查和唯一索引判断速度提升非常明显。sql_log_bin0是让当前会话的导入操作不写 binlog如果这台机器开了 binlog 并且不是复制架构中的主库这步能避免导入过程写出一大堆 binlog。注意如果有从库依赖这个主库不要关 binlog否则从库数据会缺。如果 dump 文件自己开头就设置了这些参数那不需要重复设置但检查一下总没坏处。导入过程中如果碰到报错mysql 命令行默认会继续执行下一条语句所以不要一看到错误就停下来先让它跑完再用日志梳理除非错误已经明显导致后续所有语句都会失败。4.3 导入后的对象与权限校验导入完成不等于升级完成我这里有一套固定的校验动作。首先比表数量。分别在新旧库执行SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA yourdb;数量对不上说明有表漏导或被跳过。接着比对存储过程、触发器、事件的数量这三个对象 mysqldump 不一定默认导出如果备份时漏了--routines/--triggers/--events这里会立刻发现。然后抽几张大表做行数校验。不用全部数一遍选数据量最大的三五张表各跑一次SELECT COUNT(*)新旧库对比能快速发现数据丢失的迹象。mysqldump 导出文件里其实也带每张表的行数统计在导入日志里能看到但有时不准还是直接对比靠谱。最后跑一遍CHECK TABLECHECK TABLE yourdb.table1, yourdb.table2;如果返回status | OK说明物理存储没有明显损坏。再对核心表执行一次ANALYZE TABLE更新统计信息避免 5.7 优化器因为统计信息不准选错执行计划慢查询这种事在升级后最容易出现。4.4 如果非要走就地升级逻辑迁移虽好但有些场景它就是不适合数据量大到转移耗时太长、磁盘空间只够放一份数据、停机窗口压缩到分钟级。这时候只能考虑就地升级。必须明确一点官方路线是 5.5 - 5.6 - 5.7分两步走不要试图拿 5.7 的 mysqld 直接启动 5.5 的数据目录。大致流程是先完整备份数据目录和 my.ini停掉 5.5 服务把 5.6 的二进制覆盖到同一目录或替换 basedir确保 my.ini 里的 datadir 指向原数据目录启动服务运行mysql_upgrade -uroot -p --force确认无误后停服务再换 5.7 重复同样的流程。每跑完一次mysql_upgrade都要检查系统表是否升级成功、是否有报错。Windows 上做就地升级还有一个需要特别警惕的坑安装器或初始化命令可能会往原数据目录写入新的系统表文件导致旧数据被破坏。所以无论哪种方式数据目录的物理备份都必须在动手前完成。我自己只有在实在腾不出逻辑迁移时间的时候才走就地升级能选逻辑迁移就绝不选它。5. 启动、验证与性能优化5.1 切换端口与应用连库新库在 3307 端口验证通过后终于到了切换环节。先把新旧两个服务都停掉net stop MySQL57 net stop MySQL55然后把新实例 my.ini 里的port3307改成3306确认旧服务没有残留占用 3306 端口再启动新服务net start MySQL57启动后立刻检查监听端口netstat -ano | findstr :3306看到LISTENING并且进程对应的是 mysqld才算切换成功。如果 3306 被其他程序占用先搞清楚是什么占的别强力杀进程很可能是旧 MySQL 服务没完全停掉。应用侧的切换就比较简单了把连接串里的端口从 3307 改回 3306或者如果你的应用一直用的是 3306那此时什么都不用改直接重连即可。但这里我建议先让少量测试请求打到新库确认读写、登录、定时任务都没问题再把全量流量放过去。如果应用有连接池改完端口后记得清一下连接池里的旧连接否则应用会拿着一堆失效连接反复报错。ODBC、JDBC、PHP 各自的连接池缓存方式不同但原理都一样。5.2 升级后第一时间做的健康检查新库跑起来后不要急着下班先做几项检查。看运行状态和关键变量SHOW GLOBAL STATUS LIKE Uptime; SHOW GLOBAL STATUS LIKE Aborted_connects; SHOW VARIABLES LIKE innodb_buffer_pool_size; SHOW VARIABLES LIKE max_connections;重点确认innodb_buffer_pool_size已经生效而不是默认的 128M。很多 5.5 老机器内存不小但升级后性能反而变慢八成就出在这。Aborted_connects如果持续增加可能是有客户端用老认证方式连接失败去排查驱动版本。其次是时区老应用常遇到“数据库时间和业务时间差 8 小时”的问题。5.7 的默认time_zone仍然是 SYSTEM一般不会主动变但如果你的连接池或驱动在 5.7 下重新初始化了会话结果可能不同。Java 连接串里最好显式带上serverTimezoneAsia/Shanghai一劳永逸。再就是把核心业务的 SQL 拿出来EXPLAIN一遍重点看有没有索引失效。一个非常隐蔽的现象是旧库表字符集是 utf8新库表字符集是 utf8mb4两个表 JOIN 时如果字符集或排序规则不一致优化器会让关联列各自转换索引直接失效全表扫描。这类问题靠肉眼看 SQL 看不出来只能通过执行计划发现。5.3 从 5.5 带来的“脏东西”怎么清理老库普遍有一些历史遗留问题升级是清理它们的好时机。第一把该转 InnoDB 的 MyISAM 表转掉。执行ALTER TABLE t ENGINEInnoDB;前先评估磁盘空间和耗时大表会重建可能锁较长时间。MyISAM 表上如果有 FULLTEXT 索引转 InnoDB 后要检查全文检索语法是否还兼容InnoDB 的全文索引参数和 MyISAM 不太一样。第二重建统计信息和索引。5.5 时代的一些表碎片很多逻辑导入相当于物理重建了表所以一般不需要额外 OPTIMIZE但跑一遍也没什么坏处。如果有些表是 5.5 时代长期不用的归档表导入后可以先FLUSH TABLE或干脆和业务确认是否能归档删除减少后续维护成本。第三检查自增主键。5.7 的 InnoDB 自增计数器在重启后是根据当前最大值计算的如果旧库删过大量数据最大自增 ID 已经很大而 5.7 重启后可能重新从更大的值开始也可能和旧计数器不一致。如果业务对主键连续性有要求切库后及时执行ALTER TABLE t AUTO_INCREMENTN;把起始值设回去。6. 常见问题速查与避坑清单6.1 报错与处理速查表把这次升级前后遇到过的、以及社区里高频出现的报错整理成一张表方便排查时直接查报错信息可能原因处理建议ERROR 1071: Specified key was too long; max key length is 767 bytesutf8mb4 索引列长度超了COMPACT 行格式限制 767 字节设置innodb_default_row_formatdynamic或执行ALTER TABLE t ROW_FORMATDYNAMIC;再不行就缩短索引列长度ERROR 1067: Invalid default value for xxx时间字段默认值用了0000-00-00 00:00:00被严格模式拦截临时去掉NO_ZERO_DATE和NO_ZERO_IN_DATE或者修改字段默认值Expression #N of SELECT list is not in GROUP BY clauseONLY_FULL_GROUP_BY严格模式拦截了旧 SQL修改 SQL 让分组列出现在 GROUP BY 中或临时去掉该模式ERROR 1449: There is no rootlocalhost registered视图、存储过程的 definer 用户在新库不存在先创建对应账号或对 dump 文件中的DEFINER做替换ERROR 1045: Access denied for user密码错误、账号 host 不匹配、旧密码认证协议不被支持确认连接 host 和密码必要时用ALTER USER重置认证方式为mysql_native_passwordERROR 2003: Cant connect to MySQL server服务没启动、端口不对、防火墙拦截检查net start服务列表netstat -ano | findstr :3306查看监听确认防火墙放行ERROR 1055: Expression #N of ORDER BY clause is not in GROUP BY clause同 ONLY_FULL_GROUP_BY 问题涉及 ORDER BY要么修改 SQL要么临时关掉ONLY_FULL_GROUP_BYPacket too largemax_allowed_packet太小服务端和客户端都调大重启服务Unknown table mysql.proc 或 mysql_upgrade 报错旧系统表和新版本不匹配逻辑迁移场景基本不会遇到就地升级时需要完整走 5.6 - 5.7 两步流程6.2 那些容易忽略的“隐形坑”没有数据行数校验就直接下线旧库是我见过最多次的翻车现场。如果导入过程中某个表因为报错被跳过但你没细看日志新库表面上有这张表实际数据是空的。所以不管多急行数校验不能省至少大表必须对比。另一个坑是导入时连接字符集和 dump 文件编码不一致。文件是 UTF-8但你没指定--default-character-setutf8mb4导入后中文可能变问号。遇到乱码别急着骂 MySQL先检查是不是连接字符集问题。反过来也一样如果旧库数据本身就是 latin1 或 gbk却用 utf8mb4 去 source结果一样是乱码。还有一点容易被忽略mysqldump 文件里如果有CREATE DATABASE和USE语句mysql 命令行执行 source 时它会切库到你备份时指定的库。如果手滑连接到一台已经存在同名库的机器文件里的DROP TABLE IF EXISTS可能把表干掉了。所以导入前一定要确认目标库名和目标机器最好在一台刚初始化的干净实例上操作。6.3 我的几点实操习惯平时帮人做这类升级多了我自己养成了一套固定习惯最后分享出来。第一mysqldump 导出完成后不要只盯着一行Dump completed一定要看文件大小和末尾是否有报错。曾经遇到过导出进行到一半网络中断命令没提示、文件也生成了但内容不完整导入后才发现少表。第二新实例导入数据期间把 Windows 的休眠和自动更新都临时关掉避免半夜机器自己重启。第三旧库的服务不要急着重装或删除哪怕新库已经稳定运行一周。我就遇到过新库跑了三天后某个业务定时任务才暴露问题而旧库已经清理干净的情况那叫一个难受。保留旧库到业务回归完全结束再回头清理不迟。MySQL 5.5 到 5.7 的升级真正危险的不是数据本身而是那些你以为不会变的默认行为。字符集、sql_mode、索引长度、存储引擎行格式随便一个差异都可能让应用在凌晨两点崩给你看。我的做法始终是先备份、再导新库验证、最后才切流量宁可整个过程慢一点也要给回滚留出后路。如果你也在准备 Windows 10 下的 MySQL 5.5 升级建议把上面这些检查项整理成清单一项项勾完再动手。