MySQL无锁修改字段类型

怎么通过工具无锁给Mysql修改表的字段类型

Posted by Jason on December 6, 2025

在线上服务,我们的表通常都需要一直保持稳定的可用状态。不可以接受表级别的锁长时间。

但是如果想要变更修改字段的数据类型,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记录到的这段时间的表更也追加进去。

更改表名。

就完成了。整个过程,就更改表名时会锁一下,忽略不急。

参考地址