在具有索引列的大表上使用ALTER TABLE


14

我有一个带有VARCHAR(20)列的大表,我需要对其进行修改以成为VARCHAR(50)列。通常,在此特定表上执行ALTER TABLE(添加TINYINT)大约需要90-120分钟才能完成,因此,我实际上只能在周六或周日晚上这样做,以免影响数据库的用户。如果可能的话,我想在此之前进行此修改。

该列也已建立索引,我认为这会使ALTER TABLE变慢,因为它必须在修改列长后重建索引。

该Web应用程序是在MySQL复制环境中设置的(26个从属和一个主控)。我记得曾经读过某个地方的一种方法,即首先在每个从属服务器上执行ALTER TABLE(对用户的影响最小),然后在主服务器上执行此操作,但是那不是然后尝试将ALTER TABLE命令复制到从属服务器吗?

所以我的问题是:对我来说,以最小的用户干扰来修改此表的最佳方法是什么?

编辑:该表是InnoDB。


添加那个tinyint列实际上意味着添加具有默认值的列?因为在一张大桌子上这样做可能会花费很长时间
Marian

Answers:


13

如果您有点冒险,可以通过在可见的阶段执行ALTER TABLE来解决问题。假设您要更改的表称为WorkingTable。您可以按以下步骤进行更改:

#
#  Script 1
#  Alter table structure of a single column of a large table
#
CREATE TABLE WorkingTableNew LIKE WorkingTable;
ALTER TABLE WorkingTableNew MODIFY BigColumn VARCHAR(50);
INSERT INTO WorkingTableNew SELECT * FROM WorkingTable;
ALTER TABLE WorkingTable RENAME WorkingTableOld;
ALTER TABLE WorkingTableNew RENAME WorkingTable;
DROP TABLE WorkingTableOld;

您可以在所有从站上执行此操作。那主人呢?如何防止复制到从站。简单:不要将SQL发送到主数据库的二进制日志中。在执行ALTER TABLE之前,只需在会话中关闭二进制日志记录即可:

#
#  Script 2
#  Alter table structure of a single column of a large table
#  while preventing it from replicating to slaves
#
SET SQL_LOG_BIN = 0;
CREATE TABLE WorkingTableNew LIKE WorkingTable;
ALTER TABLE WorkingTableNew MODIFY BigColumn VARCHAR(50);
INSERT INTO WorkingTableNew SELECT SQL_NO_CACHE * FROM WorkingTable;
ALTER TABLE WorkingTable RENAME WorkingTableOld;
ALTER TABLE WorkingTableNew RENAME WorkingTable;
DROP TABLE WorkingTableOld;

可是等等 !!!处理这些命令时出现的任何新数据如何处理?在操作开始时重命名表应该可以解决问题。让我们稍微修改一下此代码,以防止在这方面输入新数据:

#
#  Script 3
#  Alter table structure of a single column of a large table
#  while preventing it from replicating to slaves
#  and preventing new data from entering into the old table
#
SET SQL_LOG_BIN = 0;
ALTER TABLE WorkingTable RENAME WorkingTableOld;
CREATE TABLE WorkingTableNew LIKE WorkingTableOld;
ALTER TABLE WorkingTableNew MODIFY BigColumn VARCHAR(50);
INSERT INTO WorkingTableNew SELECT SQL_NO_CACHE * FROM WorkingTableOld;
ALTER TABLE WorkingTableNew RENAME WorkingTable;
DROP TABLE WorkingTableOld;
  • 可以在任何未启用二进制日志的从站上执行脚本1
  • 脚本2可以在任何启用了二进制日志的从站上执行
  • 脚本3可以在主服务器或其他任何地方执行

试试看 !!!


2
我看到的一个问题是表是否包含“ auto_increment”字段。我做了一个基本测试,很惊讶地看到CREATE TABLE .. LIKE不会将auto_increment值复制到新表中
Derek Downey

1
@ DTest:良好的捕获和伟大的评论!我相信您可以从information_schema.tables列AUTO_INCREMENT中检索auto_increment值。如果表中没有AUTO_INCREMENT字段,则information_schema.tables中的AUTO_INCREMENT列将为NULL。否则,它将包含所需的AUTO_INCREMENT值。我猜可以编写脚本以提取该非NULL值并执行ALTER TABLE WorkingSet AUTO_INCREMENT = <somenumber>; 在将临时表重命名回WorkingSet之前。
RolandoMySQLDBA 2011年

@RolandoMySQLDBA可以做的另一件事是,通过复制“显示创建表WorkingTable”语句来创建WorkingTableNew,并通过增加对您安全的数字来更改auto_increment字段的值,并将列更改为varchar(50 ),然后在单个语句中都重命名“将表WorkingTable重命名为WorkingTableOld,将表WorkingTableNew重命名为WorkingTable”在单个命令中运行两个重命名可确保没有插入失败(测试这是表的Prod,其插入次数为1000 / s)然后您可以执行“从...插入...”命令
Gautam Somani

4

我从文档中得出的猜测是,仅增加a的长度约束varchar不会引起与添加列相同的麻烦:

对于某些操作,可以使用不需要临时表的就地ALTER TABLE:

但这似乎与对此SO问题的评论相矛盾。

编辑

至少在5.0上,我认为我可以确认增加长度的确确实需要一个临时表(或其他一些同样昂贵的操作):

测试平台:

create table my_table (id int auto_increment primary key, varchar_val varchar(10));
insert into my_table (varchar_val)
select 'HELLO'
from (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s1,
     (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s2,
     (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s3,
     (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s4,
     (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s5,
     (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s6;

结果:

alter table my_table modify varchar_val varchar(20);
Query OK, 1000000 rows affected (2.91 sec)

alter table my_table add int_val int;
Query OK, 1000000 rows affected (2.86 sec)

修改varchar字段大小涉及检查您是否未超过新大小。为了增加尺寸,可以对支票进行优化。
BillThor

3

我想,自从 ENGINE=INNODB

如果您有外键约束,则不能更改和重命名没有指向旧表(现在已重命名)的约束。之后,您必须更改或删除持续时间的约束。


沙邦!!! 没错 在那种情况下,只能在原始表上做alter table。至少,在ALTER TABLE发生之前,您可能必须玩具有禁用约束的游戏。+1非常好!
RolandoMySQLDBA 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.