控制台 退出登录

解决Halo2.x版本升级后启动卡死,MySQL 5.7版本兼容性及索引报错解决方案

最近用 1Panel 把 Halo 升级到了 2.25.0,结果容器死活起不来。看日志发现卡在数据库连接那里不动了,健康检查也一直超时。中间重启了几次,偶尔还会爆出 Duplicate key name 'idx_xxx' 的索引重复错误。

折腾了一晚上,总算把问题理顺了,记录一下给遇到同样问题的朋友避个坑。

问题到底出在哪?

一开始看到 Duplicate key name,我以为是数据库索引冲突导致启动失败,跑去 MySQL 里把报错的索引删了,结果重启还是卡死。

后来仔细查了下资料,发现其实是两个问题叠加导致的,而且“卡死”的真凶根本不是索引报错。

1. 日志卡死不动的真凶:TLS 握手失败
官方给出的排查结论是:新版 OpenJDK 默认禁用了 TLS_RSA_* TLS 算法,导致无法和低版本 MySQL(5.7)建立 SSL 连接。
通俗点说就是,新版 Halo 镜像里的 JDK 比较新,觉得老的加密算法不安全就给禁了;但 MySQL 5.7 默认就支持这些老算法。结果就是 Halo 尝试用 SSL 连 MySQL 时,握手直接失败或者无限挂起,导致 Spring Boot 启动流程直接卡死。

2. 索引报错的原因:初始化脚本不严谨
Halo 自带的 schema-mysql.sql 脚本在创建某些索引时,没加 IF NOT EXISTS 判断。如果数据库里已经有这些索引了,它再次执行就会报 Duplicate key name 错误,阻断启动。

解决方法:

针对 TLS 握手卡死的问题,官方其实给出了三种解决方案

  1. 在启动参数中添加 --spring.r2dbc.properties.sslMode=disabled(仅对 MySQL 驱动有效)。

  2. spring.r2dbc.url 尾部添加 ?sslMode=disabled(仅对 MySQL 驱动有效)。

  3. 升级至 MySQL 8。

至于那个 Duplicate key name 的索引报错,我们需要再加一个参数 --spring.sql.init.continue-on-error=true 让脚本忽略错误继续执行。

如果你是用 Docker Compose 或者 1Panel 部署的,找到 Halo 的配置,在启动参数(command)里改一下。官方的方案 1 和方案 2 任选其一即可,我这里用的是

方案 1:

networks:
    1panel-network:
        external: true
services:
    halo:
        command:
            - --spring.r2dbc.url=r2dbc:pool:${PANEL_DB_TYPE}://${PANEL_DB_HOST}:${PANEL_DB_PORT}/${PANEL_DB_NAME}
             # 官方方案1:直接在参数里禁用 SSL(如果是方案2,就把 ?sslMode=disabled 拼到下面 url 的末尾)
            - --spring.r2dbc.properties.sslMode=disabled
              # 允许初始化脚本报错并继续,解决索引重复报错
            - --spring.sql.init.continue-on-error=true
            - --spring.r2dbc.username=${PANEL_DB_USER}
            - --spring.r2dbc.password=${PANEL_DB_USER_PASSWORD}
            - --spring.sql.init.platform=${PANEL_DB_TYPE}
            - --halo.external-url=${HALO_EXTERNAL_URL}
             #添加redis
            - --spring.data.redis.host=redis
            - --spring.data.redis.database=0
            - --spring.data.redis.port=6379
            - --spring.data.redis.password=redis密码
            - --halo.session.store-type=redis
            - --halo.redis.enabled=true
            # ... 其他参数保持原样 ...
        container_name: ${CONTAINER_NAME}
        deploy:
            resources:
                limits:
                    cpus: ${CPUS}
                    memory: ${MEMORY_LIMIT}
        environment:
            - JVM_OPTS=
        healthcheck:
            interval: 30s
            retries: 5
            start_period: 30s
            test:
                - CMD
                - curl
                - -f
                - http://localhost:8090/actuator/health/readiness
            timeout: 5s
        image: halohub/halo-pro:2.25.0
        labels:
            createdBy: Apps
        networks:
            - 1panel-network
        ports:
            - ${HOST_IP}:${PANEL_APP_PORT_HTTP}:8090
        restart: always
        volumes:
            - ./data:/root/.halo2

改完之后重启容器,再看日志,应该就能顺利通过数据库连接阶段,最后输出 Netty started on port 8090 了。

为什么不用 init.mode=never

在解决索引报错时,很多人(包括我一开始)会想到直接把数据库初始化关掉,也就是加上 --spring.sql.init.mode=never

强烈建议别这么干。

如果直接设为 never,虽然眼前的索引报错没了,但如果以后 Halo 升级,schema-mysql.sql 里加了新的基础表,系统就不会去自动建表了,到时候会引发更严重的故障。

continue-on-error=true 就稳妥得多:

  1. 遇到已存在的索引报错,它会忽略并继续往下走,不会卡死。

  2. 如果遇到真正需要新建的表(CREATE TABLE),它依然会正常执行。

  3. 至于 Halo 核心的版本升级(Flyway)和插件建表(Extension 机制),都是独立运行的,不受这个参数影响。

关于为什么不直接听官方的“升级 MySQL 8”?

看到官方给的第三个方案“升级至 MySQL 8”,肯定有人会问:既然 5.7 八字不合,为啥不直接升 8.x 一劳永逸?

实不相瞒,我这台服务器就是个 4核4G 的配置,上面还跑着一大堆其他的应用和服务。MySQL 8 那个吃内存的德行,真跑起来估计分分钟 OOM 把整台机器干宕机,4G 内存跑 MySQL 8 纯属给自己找不痛快。

再加上升级数据库还得停机、备份、倒腾数据,服务器上业务多,实在懒得折腾。所以对于我这种配置受限又不想折腾的人来说,直接用官方的方案 1 或方案 2,加个 sslMode=disabled 凑合用,才是最省事的解法。反正 Halo 和 MySQL 都在本机内网通信,关掉 SSL 也没什么安全风险,还能稍微省点 CPU 开销。

最后

启动成功后,日志里可能还会偶尔飘过几条 Duplicate key name 的 WARN 警告,不用管它,只要服务正常跑起来了就行。希望能帮到同样在半夜折腾 Halo 升级的兄弟。

本文采用 CC BY-NC-SA 4.0 协议发布

相关文章

某国企IT岗面试总结与陪跑感悟,坑里早就有萝卜了

某国企IT岗面试总结与陪跑感悟,坑里早就有萝卜了

如何为智谱GLM平台禁用Claude Code的1M上下文功能

如何为智谱GLM平台禁用Claude Code的1M上下文功能

2025最全电脑清灰攻略:从工具准备到安全操作,手把手教你零风险除尘

2025最全电脑清灰攻略:从工具准备到安全操作,手把手教你零风险除尘