1:背景描述

mysql数据库中有如下的表结构,需要关注的是status字段的类型,此次需求为想要将这个字段的枚举值中的error变更为initialize_failed

mysql> desc servers;
+-------------------+--------------------------------------------------------+------+-----+--------------+-------+
| Field             | Type                                                   | Null | Key | Default      | Extra |
+-------------------+--------------------------------------------------------+------+-----+--------------+-------+
| id                | varchar(36)                                            | NO   | PRI | NULL         |       |
| name              | varchar(36)                                            | NO   |     | NULL         |       |
| gw_id             | varchar(36)                                            | NO   |     | NULL         |       |
| client_ip_pool    | varchar(36)                                            | NO   |     | NULL         |       |
| eip_id            | varchar(36)                                            | YES  |     | NULL         |       |
| proto             | enum('UDP')                                            | YES  |     | UDP          |       |
| status            | enum('initializing','initialized','error')             | YES  |     | initializing |       |
| port              | int(11)                                                | NO   |     | NULL         |       |
| compress          | tinyint(1)                                             | YES  |     | NULL         |       |
| url               | varchar(512)                                           | YES  |     | NULL         |       |
| created_at        | datetime                                               | YES  |     | NULL         |       |
| updated_at        | datetime                                               | YES  |     | NULL         |       |
| deleted_at        | datetime                                               | YES  |     | NULL         |       |
| deleted           | bigint(20)                                             | YES  |     | NULL         |       |
+-------------------+--------------------------------------------------------+------+-----+--------------+-------+

2:问题分析

首先需要提及的是,该表所依赖的项目已经运行过一段时间,即该表中已有一定的数据,可能status属性中已有error的数据存在,毕竟突然将某个字段的类型进行变更,一定也意味着项目中的源代码也在这里做了相应的改变,db中该表该字段出现error的数据也是可以理解的。因此安全起见,查询了这个表中的status字段的状态

mysql> select * from vpn_servers where initialize_status = 'error' limit 2;
Empty set (0.00 sec)

mysql> select distinct initialize_status from vpn_servers;
+-------------------+
| initialize_status |
+-------------------+
| initialized       |
| initializing      |
+-------------------+
2 rows in set (0.00 sec)

很惊喜,这个表中这个字段的值没有error的,那么接下来就有两种方式修改这个字段的枚举值了。
(1)直接修改表结构,重定义这个枚举类型值。
(2)修改表结构,新增这个枚举类型值initialize_failed,然后再将原有的error的值修改为initialize_failed,最后再重定义枚举值删除error
注意:如果这个表中是有这个error的数据,那么一定要走方法2,如果想走方法1,那么权衡好是生产环境还是测试环境,是否可以安全地删除这个status具有error的数据,该表是否和其他表有外键?级联删除关联?,如果想要删除,一定要通过api来删除,以免在db,缓存中留下脏数据。

3:问题解决

方法(1):因为本例中我发现了这个表中没有关于这个status字段有error的数据,所以图方便,可以直接修改

alter table servers modify `status` enum ('initializing','initialized','initialize_failed') default 'initializing';

**方法(2)**开始讲方法2前,试想下下面情况,如果该表中有error的数据,如下所示

MariaDB [test_using]> select * from servers where status = 'error' \G
*************************** 1. row ***************************
            id: 12345
          name: test
         gw_id: 11111
client_ip_pool: NULL
        eip_id: 22222
        status: error
1 row in set (0.00 sec)

此时在status字段有error数据的情况下,再想像方法1那样直接更改会出现不可预知的问题,比如本例中,直接更改,导致原来的error的数据直接变为空(不是null),此时如果是生产环境,一个小小的螺丝钉的问题就有可能会产生一系列重大事故。

MariaDB [test_using]> alter table servers modify `status` enum ('initializing','initialized','initialize_failed') default 'initializing';
Query OK, 1 row affected, 1 warning (0.02 sec)     
Records: 1  Duplicates: 0  Warnings: 1

MariaDB [test_using]> select * from servers where status = 'error' \G
Empty set (0.00 sec)

MariaDB [test_using]> 
MariaDB [test_using]> 
MariaDB [test_using]> select * from servers  \G
*************************** 1. row ***************************
            id: 12345
          name: test
         gw_id: 11111
client_ip_pool: NULL
        eip_id: 22222
        status: 
1 row in set (0.00 sec)

因此,正确且安全的方式如下
先对该表enum字段新增一个新的类型值

alter table servers modify `status` enum ('initializing','initialized','error','initialize_failed') default 'initializing';

接着update该表,将原有的要被替换掉的类型值error的数据变为新的类型值’initialize_failed’

update servers set status = 'initialize_failed' where status = 'error';

最后,再次变更表中status的enum的类型值,去掉error

alter table servers modify `status` enum ('initializing','initialized','initialize_failed') default 'initializing';

查询数据后

MariaDB [test_using]> select * from servers  \G
*************************** 1. row ***************************
            id: 12345
          name: test
         gw_id: 11111
client_ip_pool: NULL
        eip_id: 22222
        status: initialize_failed
1 row in set (0.00 sec)

4:后记

其实还有一种最不推荐的方法,那就是删表重建,适用于线下环境,或者表中数据都是垃圾数据的情形下,删掉也不心疼。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐