之前用python写了个脚本,用于比对test和prod的表结构差异(防止出现上prod的时候,发生表或者索引遗漏的情况)。
但是还不够友好,只能找出差异但是不能自动生成fix的SQL。
这里再介绍一个小工具 skeema,它的免费版的功能已经足够强大,可以自动找出差异,并给出fix的语句。(当然这个工具在某些场景下也有致命的缺点,具体见本文的最后部分)
看下面的例子:
代码语言:javascript复制https://www.skeema.io/docs/
curl -LO https://github.com/skeema/skeema/releases/latest/download/skeema_amd64.rpm
yum localinstall skeema_amd64.rpm 或者直接下载二进制bin文件
我们这里是比对test环境和uat环境的表结构差异
需要的权限:
test环境:
create user user1@'%' identified by 'pass1';
grant select on *.* to user1@'%';
grant ALL PRIVILEGES on `_skeema_tmp`.* to user1@'%'; -- 比对操作需要在这个库进行临时读写操作。给了ALL后,账号就可以只创建和CRUD操作_skeema_tmp库,不会影响到其他业务库
uat环境:
create user user2@'%' identified by 'pass2';
GRANT SELECT ON *.* TO `user2`@`%`;
GRANT ALL PRIVILEGES ON `_skeema_tmp`.* TO `user2`@`%`; -- 比对操作需要在这个库进行临时读写操作。给了ALL后,账号就可以只创建和CRUD操作_skeema_tmp库,不会影响到其他业务库
# 在TEST环境执行, 获取uat数据并写到 sbtest这个文件夹下
[root@centos7-3 ~]# cd lirulei/
[root@centos7-3 lirulei]# skeema init -h 192.168.1.182 -P 3306 -u user1 -p'pass1' -d sbtest --schema sbtest
[root@centos7-3 skeema]# ll
total 12
drwxr-xr-x 2 root root 12288 Aug 31 15:52 sbtest
# 进到sbtest目录下,编辑下当前目录的隐藏文件
[root@centos7-3 skeema]# cd sbtest
[root@centos7-3 sbtest]# cat .skeema
default-character-set=utf8
default-collation=utf8_general_ci
generator=skeema:1.10.1-community
schema=sbtest
[test] -- 原先这里是production,改为了test
flavor=mysql:8.0
host=192.168.1.182
port=3306
user=user1
在sbtest目录下执行
[root@centos7-3 sbtest]# skeema add-environment uat -h 192.168.1.181 -P 3306 -u user2 -p'pass2'
2023-08-31 15:54:13 [INFO] Added environment [uat] to /root/user2/skeema/sbtest/.skeema
[root@centos7-3 sbtest]# cat .skeema
default-character-set=utf8
default-collation=utf8_general_ci
generator=skeema:1.10.1-community
schema=sbtest
[test]
flavor=mysql:8.0
host=192.168.1.182
port=3306
user=user1
[uat]
flavor=mysql:8.0
host=192.168.1.181
port=3306
user=user2
在sbtest目录下,拉取下test环境的表结构(防止在init后,又出现了改动)
[root@centos7-3 sbtest]# skeema pull test -p'pass1' # 这里输入test的密码
2023-08-31 16:11:54 [INFO] Updating /root/user2/skeema/sbtest to reflect 192.168.1.182:3306 sbtest
2023-08-31 16:02:29 [INFO] Updating /root/user2/skeema/sbtest to reflect 192.168.1.182:3306
生成差异sql文件
[root@centos7-3 sbtest]# skeema diff uat --allow-unsafe -p'pass2' > /tmp/uat_to_fixed.sql # 这里输入的是uat的密码
2023-08-31 16:02:54 [WARN] /root/user2/skeema/sbtest/tb1.sql:28: Table
tb1 is using default character set utf8, which is not
configured to be permitted. The following character sets are listed in option
allow-charset: latin1, utf8mb4.
To permit storage of all valid four-byte UTF-8 characters, use the utf8mb4
character set instead of the legacy three-byte utf8 character set.
2023-08-31 16:02:54 [WARN] /root/user2/skeema/sbtest/tb2.sql:21: Table
tb2 is using default character set utf8, which is not configured
to be permitted. The following character sets are listed in option allow-charset:
latin1, utf8mb4.
To permit storage of all valid four-byte UTF-8 characters, use the utf8mb4
character set instead of the legacy three-byte utf8 character set.
2023-08-31 16:02:54 [INFO] 192.168.1.181:3306 sbtest: diff complete
生成的差异文件类似如下:
-- instance: 192.168.1.181:3306
USE `sbtest`;
ALTER TABLE `t1` ADD COLUMN `address` varchar(255) NOT NULL DEFAULT '' COMMENT 'xxxx';
DROP TABLE `t2`;
ALTER TABLE `t3` DROP COLUMN `col1`, MODIFY COLUMN `col2` char(2) NOT NULL DEFAULT '' COMMENT '类型', MODIFY COLUMN `col3` varchar(64) NOT NULL DEFAULT '' COMMENT 'xx', ADD COLUMN `col4` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT 'xxxxx' AFTER `name`;
拿到DDL语句后,就可以提工单将这些变更同步到UAT环境了。
TIPS:
1、 我们也可以把密码写到配置文件里,这样命令行就不用输入了,例子如下:
[root@centos7-3 sbtest]# cat .skeema
default-character-set=utf8
default-collation=utf8_general_ci
generator=skeema:1.10.1-community
schema=sbtest
[test]
flavor=mysql:8.0
host=192.168.1.182
port=3306
user=user1
password='pass1'
[uat]
flavor=mysql:8.0
host=192.168.1.181
port=3306
user=user2
password='pass2'
2、官方文档上提到还支持push操作,直接把变更的命令发到目标mysql去执行,这个操作实际上还是有点危险的,不建议使用。
感兴趣的,可通过binlog分析下 skeema的过程:
代码语言:javascript复制
执行 init后,会从src上把相关的建表语句拉到本地文件夹下(执行show create table xxx)
执行 pull后,会把src上的差异的建表语句拉到本地文件夹下(执行show create table xxx)
执行 diff后,会把src上的建表语句下发到dest的 _skeema_tmp 这个临时创建的库里,然后进行比对,并生成修改的SQL语句【具体的比对逻辑详见代码仓库 https://github.com/skeema/skeema】
目前不支持的地方[非常重要]:
代码语言:javascript复制https://www.skeema.io/docs/requirements/
1 不支持 ALTER TABLE
Skeema 可以使用这些功能创建或删除表,但无法生成操作它们的 ALTER。skeema diff和的输出skeema push将注意到它无法为涉及这些功能的表生成或运行 ALTER TABLE,因此将跳过受影响的表,但其余操作将照常进行。
空间索引
子分区(同一个表中的两级分区)
常规表空间(除innodb_systemor之外的显式 TABLESPACE 子句innodb_file_per_table)
MariaDB 的应用程序时间段功能(PERIOD FOR子句)
非InnoDB存储引擎的一些特性
2 重命名列或表
Skeema 目前无法用于重命名表中的列,或重命名整个表。这是 Skeema 声明式方法的一个缺点:通过将所有内容表示为 a CREATE TABLE,Skeema 无法(绝对确定)知道列重命名与删除现有列和添加新列之间的区别。重命名表也存在类似的问题。
未来版本中可能会添加解决方案。优先级将取决于用户需求。无论如何,许多公司都不允许在生产中进行重命名,因为它们会带来相当大的部署顺序复杂性:不可能在数据库中的列或表重命名的同时部署应用程序代码更改。
目前,Skeema 会将重命名尝试解释为 DROP-then-ADD 操作。但由于 Skeema 自动将任何破坏性操作标记为不安全,因此将阻止执行这些操作,除非使用allow-unsafe 选项,或者表低于safe-below-size 选项中指定的大小限制。
如下例子1 (对于列的rename,会被转成drop column add column):
test环境执行的是: alter table t3333 change name stu_name char(120);
Skeema自动转换出来的SQL:
ALTER TABLE `t3333` DROP COLUMN `name`, ADD COLUMN `stu_name` char(120) DEFAULT NULL;
可以看到这样在生产环境是没法直接使用的,如果这样搞的话,原始name列的数据就全丢失了。
如下例子2(对于表的rename,会被转成drop create):
test环境执行的是: rename table t3333 to t4444;
Skeema自动转换出来的SQL:
DROP TABLE `t3333`;
CREATE TABLE `t4444` (
`id` int(11) NOT NULL,
`stu_name` char(120) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
3 不支持视图的捕获(比对会自动忽略掉视图)
4 对外键的检测基本支持有限(生产上外键一般用的很少)
5 对分区表的支持也有限,看下面的例子
源端执行:
CREATE TABLE sales (
money INT UNSIGNED NOT NULL,
`date` DATETIME
) ENGINE=INNODB
PARTITION BY RANGE (YEAR(DATE)) (
PARTITION p2008 VALUES LESS THAN (2009),
PARTITION p2009 VALUES LESS THAN (2010),
PARTITION p2010 VALUES LESS THAN (2011)
);
alter table sales drop partition p2008;
alter table sales ADD PARTITION (PARTITION p2011 VALUES LESS THAN (2012));
Skeema自动转换出来的SQL:
CREATE TABLE `sales` (
`money` int(10) unsigned NOT NULL,
`date` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
/*!50100 PARTITION BY RANGE (year(`date`))
(PARTITION p2008 VALUES LESS THAN (2009) ENGINE = InnoDB,
PARTITION p2009 VALUES LESS THAN (2010) ENGINE = InnoDB,
PARTITION p2010 VALUES LESS THAN (2011) ENGINE = InnoDB) */;
可以看到 虽然能捕获到 创建分区表的操作,但是后面的 drop partition的操作没有被识别捕获到。
6 社区版对触发器的支持有限(基本上生产也很少用触发器,问题不大)