在线上服务,我们的表通常都需要一直保持稳定的可用状态。不可以接受表级别的锁长时间。
但是如果想要变更修改字段的数据类型,Mysql直接操作又会锁表,且时间较长。
在这种场景,我应该如何处理啦?
本文的处理方式是演示如何通过gh-ost完成无锁迁移的过程。
前提条件
- 你要有一定的Mysql知识
- 你电脑上安装了docker(请确保自己可以正常pull docker image,自己搞定)
步骤
先直接演示,后面再讲原理。
1. 在本机运行Mysql
docker run --name mysql57 \
-e MYSQL_ROOT_PASSWORD=123456 \
-p 3306:3306 \
-d mysql:5.7 \
--server-id=1 \
--log-bin=mysql-bin \
--binlog-format=row \
--gtid-mode=ON \
--enforce-gtid-consistency=ON
2. 进入Mysql,创建数据表和数据
# 进入容器
docker exec -it mysql57 /bin/bash
# 连接mysql
mysql -uroot -p123456
# 创建数据库
CREATE DATABASE testdb;
# 使用数据库
USE testdb;
# 创建一张数据表
CREATE TABLE big_table (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
info VARCHAR(100),
created_at DATETIME,
key `idx_user_id` (user_id)
);
# 插入500万行数据
INSERT INTO big_table (user_id, info, created_at)
SELECT
FLOOR(RAND()*100000),
CONCAT('info_', FLOOR(RAND()*1000000)),
NOW()
FROM
information_schema.columns a,
information_schema.columns b
LIMIT 5000000;
3. 有锁修改表结构
为了演示效果,这里要开启两个命令窗口看效果。 A窗口,执行表变更用 B窗口,插入数据,查看是否有锁
先打开一个窗口B,进入数据库
# B窗口 - 进入mysql容器-连接数据库
docker exec -it mysql57 mysql -uroot -p123456
# B窗口 - 进入mysql容器-使用数据库
USE testdb;
准备同时执行看效果
# B窗口 - 先插入一条数据(此时无锁,秒执行结束)
INSERT INTO big_table (user_id, info, created_at) VALUES (1, 'insert_test', NOW());
# A窗口 - 执行结构变更 (锁表,执行中 大约10秒左右吧 取决于电脑性能)
ALTER TABLE big_table MODIFY COLUMN user_id BIGINT NOT NULL;
# B窗口 - 再插入一条数据(此时表锁,等待锁结束)
INSERT INTO big_table (user_id, info, created_at) VALUES (1, 'insert_test', NOW());
4. 无锁修改表结构
A窗口exit,回到linux命令行,下载gh-ost
# 退出mysql
exit
# 下载gh-ost (网络环境访问不了github的自己想办法)
# 你可以试试这个地址: https://doget.nocsdn.com/#/
curl -O https://github.com/github/gh-ost/releases/download/v1.1.7/gh-ost-binary-linux-amd64-20241219160321.tar.gz
# 解压
tar -xzvf gh-ost-binary-linux-amd64-20241219160321.tar.gz
# 赋予可自行权限
chmod +x ./gh-ost
# 执行无锁迁移
./gh-ost --host=127.0.0.1 \
--port=3306 \
--user=root \
--password=123456 \
--database=testdb \
--table=big_table \
--verbose \
--alter="MODIFY COLUMN user_id INT NOT NULL" \
--allow-on-master \
--execute
# B窗口-插入数据试试(此时因为没有表锁,可以直接插入数据)
INSERT INTO big_table (user_id, info, created_at) VALUES (1, 'newb', NOW());
# B窗口-正常查数据
select * from big_table where user_id = 1;
演示结束。把你的环境删了吧。
# 停止mysql容器
docker stop mysql57
# 删除容器
docker rm mysql57
原理
原理看这里,它讲得比我清楚多了 https://github.com/github/gh-ost?tab=readme-ov-file#how
什么?你看不懂英文?
好的,我帮你翻译成中文贴在这里了。
binlog日志都懂吧?
首先,gh-ost假装自己是一台mysql从服务器,连接上你的主服务器。
然后,创建一张和原表结构相同的表出来,在这张空表上面修改你要修改的字段。
然后,通过binlog日志,不断记录原本的变更数据。
然后,把原本的所有数据写进新表中去。
然后再把binlog记录到的这段时间的表更也追加进去。
更改表名。
就完成了。整个过程,就更改表名时会锁一下,忽略不急。