一次 MySQL 迁移过程中碰到的奇异事件

记录了一次将 MySQL 由 5.7 版本迁移 到 8.0 版本的过程,以及迁移过程中所遇到的奇怪的现象。我有点怀疑是 MySQL 5.7 的环境变量设置引起的问题,但我没有证据!

目标环境

目标系统为典型的单机 LNMP 架构,另有 Redis 用作缓存、及计数器。具体情况是这样的:

  • 操作系统:CentOS 7
  • 数据库:MySQL 5.7.21,服务端口 3306。通过 SCLo 用 yum 安装的。
  • 应用框架:Yii2 2.0.34(一框架。奇迹就在这里发生!)

任务是升级 MySQL 数据库为 8.0 版本。问:5.7 用得好好的,为什么要升级?答曰:想要随心所欲地使用开窗函数。嗯,这理由倒是很充分!在 MySQL 5.7 下,要想实现类似的功能、那不是一点的麻烦!一个简单的涨跌比较查询,得写一长串“套娃”似的、多层嵌套的 SQL语句,不好写!容易出错!执行时间感人!

准备条件

实施步骤

由于频繁写盘的浏览计数、点赞计数等另外采用了 Redis 来存储,因此 MySQL 升级简单了许多。决定采取的方案是:

  1. 备份现有数据库(包括 Redis)。
  2. 在系统中安装 MySQL 8.0 服务器,运行在不同的端口,如 3316。
  3. 在新的 MySQL 8.0 服务器中创建并导入之前备份的数据库。因为系统中现在有 2 个 MySQL 版本,这里要注意下 MySQL 客户端命令 mysql 的路径。
  4. 修改应用框架配置中的数据库 DSN 中的端口,改为 MySQL 8.0 的端口 3316。
  5. 清理应用系统缓存。对应 Yii2 的命令是: ./yii cache/flush cache1 cache2 # 刷新缓存组件。
  6. 按说,这时候再访问应用,读写应该会切换到新的 MySQL 8.0 数据库上。好的,新建一篇文章,保存。马上查看 MySQL 8.0 数据库中文章表是否有相应更新——没有!查看 5.7 数据库中的相关表——有了,有刚才新增的文章记录,说明读写并没有切换到新的 8.0 数据库。
  7. 再清理应用系统缓存,这次彻底点: ./yii cache/flush-all # 刷新应用中所有的缓存组件;再执行第 6 步,结果还是一样,新增的文章还是保存到了 5.7 版本的数据库中。
  8. 那咱换另一个应用试试,换 “matomo”:修改 ./config/config.ini.php,在 “[database]” 下增加1行:“port=3316”;呃,新增的记录确实是保存到新的 8.0 数据库中了。
  9. 交换两个版本数据库的端口:5.7 数据库服务端口改为 3316;8.0 数据库服务端口改为 3306;对应把 Yii 框架应用配置中的数据库 DSN 中的端口改回 3306(或默认配置)。再从第 5 步重新执行。灵异的事情发生了,新增的文章还是保存到 5.7 版本的数据库中去了。到这里,好像可以得出一个结论:本例 Yii2 框架中的数据库 DSN 配置中,增加 “port=xxxx” 并未起作用。把 DSN 改为 用 socket 的情况也试过了,结果都是一样:Yii 框架还是会继续操作旧有的 5.7 版本数据库。

问题分析

那么 Yii2 框架的数据库连接配置 DSN 中到底支不支持 "port" 呢?

查询 Yii2 源码,DSN 的定义是这样的:

// vendor/yiisoft/yii2/db/Connection.php
// ...
    /**
     * @var string the Data Source Name, or DSN, contains the information required to connect to the database.
     * Please refer to the [PHP manual](https://secure.php.net/manual/en/pdo.construct.php) on
     * the format of the DSN string.
     *
     * For [SQLite](https://secure.php.net/manual/en/ref.pdo-sqlite.connection.php) you may use a [path alias](guide:concept-aliases)
     * for specifying the database path, e.g. `sqlite:@app/data/db.sql`.
     *
     * @see charset
     */
    public $dsn;

Yii 框架数据库抽象层基于 PDO,驱动中 PDO_MYSQL DSN 是这样定义的:

The PDO_MYSQL Data Source Name (DSN) is composed of the following elements:
DSN prefix
The DSN prefix is mysql:.
host
The hostname on which the database server resides.
port
The port number where the database server is listening.
dbname
The name of the database.
unix_socket
The MySQL Unix socket (shouldn't be used with host or port).
charset
The character set. See the character set concepts documentation for more information.

这说明在 MySQL DSN 配置中应该是支持 "port" 元素的。

应急解决方案

产品环境下,已经没有更多的时间去测试了。在重新备份了最新的数据之后,我删除了 MySQL 5.7 的所有安装包,然后通过 SCLo 安装了 MySQL 8.0

# yum --enablerepo=centos-sclo-rh -y install rh-mysql80-mysql-server
# vi /etc/opt/rh/rh-mysql80/my.cnf.d/mysql-server.cnf
# 在 [mysqld] 配置块的最后一行下面增加
[mysqld]
# ...
character-set-server=utf8mb4
# 保存退出
# systemctl enable --now rh-mysql80-mysqld
Created symlink from /etc/systemd/system/multi-user.target.wants/rh-mysql80-mysqld.service to /usr/lib/systemd/system/rh-mysql80-mysqld.service.

从 SCLo 安装的 MySQL 会安装在 /opt 目录下。要正常使用,需要设置环境变量

# vi /etc/profile.d/rh-mysql80.sh   
# create new
source /opt/rh/rh-mysql80/enable
export X_SCLS="`scl enable rh-mysql80 'echo $X_SCLS'`"                                                                                                                                                                                                        
~                                                                                                                                                                                                             
"rh-mysql80.sh" [New] 3L, 101C written
# scl enable rh-mysql80 bash
# source /etc/profile
# mysql -V           
mysql  Ver 8.0.17 for Linux on x86_64 (Source distribution)

接下来,初始化数据库,设置权限,导入数据库......等等这些按照常规操作即可。

注意事项

经过 SCLo 安装的 MySQL 5.7 版本,要记得自己有没有为 MySQL 5.7 版本手工建立过环境变量设置文件?如有也需要删除。我曾在 /etc/profile.d 目录下建立过一个 rh-mysql57.sh,它的内容同上面的 rh-mysql80.sh 类似,那么在 source /etc/profile 之前要先把它删除。

后记

经过 SCLo 安装的 MySQL 8.0 运作正常,题主已经幸福地用上了 MySQL 开窗系列函数,省下写 SQL 的时间、可以用来愉快地摸(分)鱼(析)了。目测,在不与其他表连表的情况下,一个数百万行的表内做连续涨跌查询时间基本上在 2 秒之内。

那么问题究竟出在哪里呢?我现在还没有找到。我有点怀疑是 MySQL 5.7 的环境变量设置引起的问题,但我没有证据!等有时间,再在虚拟机里复盘一下,看看问题是否还会复现。对于有同样升级需求的朋友,则建议把这一步做在前面,先在虚拟机环境测试,确定方案 ok,再在产品环境实施迁移。

附件

有点怀疑是 MySQL 5.7 系统服务文件 引起的问题,后附如下:

# vi /usr/lib/systemd/system/rh-mysql57-mysqld.service
# It's not recommended to modify this file in-place, because it will be
# overwritten during package upgrades.  If you want to customize, the
# best way is to create a file "/etc/systemd/system/rh-mysql57-mysqld.service",
# containing
#       .include /usr/lib/systemd/system/rh-mysql57-mysqld.service
#       ...make your changes here...
# or create a file "/etc/systemd/system/rh-mysql57-mysqld.service.d/foo.conf",
# which doesn't need to include ".include" call and which will be parsed
# after the file rh-mysql57-mysqld.service itself is parsed.
#
# For more info about custom unit files, see systemd.unit(5) or
# http://fedoraproject.org/wiki/Systemd#How_do_I_customize_a_unit_file.2F_add_a_custom_unit_file.3F
# For example, if you want to increase mysql's open-files-limit to 10000,
# you need to increase systemd's LimitNOFILE setting, so create a file named
# "/etc/systemd/system/rh-mysql57-mysqld.service.d/limits.conf" containing:
#       [Service]
#       LimitNOFILE=10000
# Or if you require to execute pre and post scripts in the unit file as root, set
#       PermissionsStartOnly=true
# Note: /usr/lib/... is recommended in the .include line though /lib/...
# still works.
# Don't forget to reload systemd daemon after you change unit configuration:
# root> systemctl --system daemon-reload
[Unit]
Description=MySQL 5.7 database server
After=syslog.target
After=network.target
[Service]
Type=forking
User=mysql
Group=mysql
PIDFile=/var/run/rh-mysql57-mysqld/mysqld.pid
# Load collections set to enabled for this service
EnvironmentFile=/opt/rh/rh-mysql57/service-environment
# We want to start server only inside "scl enable" invocation
ExecStartPre=/usr/bin/scl enable $RH_MYSQL57_SCLS_ENABLED -- /usr/bin/scl_enabled rh-mysql57
ExecStartPre=/usr/bin/scl enable $RH_MYSQL57_SCLS_ENABLED -- /opt/rh/rh-mysql57/root/usr/libexec/mysql-check-socket
ExecStartPre=/usr/bin/scl enable $RH_MYSQL57_SCLS_ENABLED -- /opt/rh/rh-mysql57/root/usr/libexec/mysql-prepare-db-dir %n
# Note: we set --basedir to prevent probes that might trigger SELinux alarms,
# per bug #547485
ExecStart=/opt/rh/rh-mysql57/root/usr/libexec/mysqld-scl-helper enable $RH_MYSQL57_SCLS_ENABLED -- /opt/rh/rh-mysql57/root/usr/libexec/mysqld --daemonize --basedir=/opt/rh/rh-mysql57/root/usr --pid-file=/va
r/run/rh-mysql57-mysqld/mysqld.pid
ExecStartPost=/usr/bin/scl enable $RH_MYSQL57_SCLS_ENABLED -- /opt/rh/rh-mysql57/root/usr/libexec/mysql-check-upgrade
ExecStopPost=/usr/bin/scl enable $RH_MYSQL57_SCLS_ENABLED -- /opt/rh/rh-mysql57/root/usr/libexec/mysql-wait-stop
# Give a reasonable amount of time for the server to start up/shut down
TimeoutSec=300
# Place temp files in a secure directory, not /tmp
PrivateTmp=true
Restart=on-failure
RestartPreventExitStatus=1
[Install]
WantedBy=multi-user.target


喜歡:
0

To the top