MySQL-用于InnoDB的ALTER TABLE的最快方法


12

我有一个要更改的InnoDB表。该表有约8000万行,并退出了一些索引。

我想更改其中一列的名称并添加更多索引。

  • 最快的方法是什么(假设即使停机,我也可能会遭受痛苦-服务器是未使用的从属服务器)?
  • 是“普通” alter table的最快解决方案吗?

此时,我只关心速度:)


SHOW CREATE TABLE tblname\G,显示需要更改的列,该列的数据类型以及该列的新名称。
RolandoMySQLDBA 2011年

这就是:pastie.org/3078349需要被重新命名为列sent_at,并给它添加一些指标

send_at需要重命名为什么?
RolandoMySQLDBA 2011年

比方说:new_sent_at

Answers:


14

加快ALTER TABLE速度的一种可靠方法是删除不必要的索引

这是加载表的新版本的初始步骤

CREATE TABLE s_relations_new LIKE s_relations;
#
# Drop Duplicate Indexes
#
ALTER TABLE s_relations_new
    DROP INDEX source_persona_index,
    DROP INDEX target_persona_index,
    DROP INDEX target_persona_relation_type_index
;

请注意以下事项:

  • 我删除了source_persona_index,因为它是其他4个索引的第一列

    • unique_target_persona
    • unique_target_object
    • source_and_target_object_index
    • source_target_persona_index
  • 我删除了target_persona_index,因为它是其他2个索引的第一列

    • target_persona_relation_type_index
    • target_persona_relation_type_message_id_index
  • 我删除了target_persona_relation_type_index,因为前两列也在target_persona_relation_type_message_id_index中

确定这样可以处理不必要的索引。有没有低基数的索引?这是确定的方法:

运行以下查询:

SELECT COUNT(DISTINCT sent_at)               FROM s_relations;
SELECT COUNT(DISTINCT message_id)            FROM s_relations;
SELECT COUNT(DISTINCT target_object_id)      FROM s_relations;

根据您的问题,大约有80,000,000行。根据经验,如果所选列的基数大于表行计数的5%,则MySQL Query Optimizer将不使用索引。在这种情况下,将为4,000,000。

  • 如果COUNT(DISTINCT sent_at)> 4,000,000
    • 然后 ALTER TABLE s_relations_new DROP INDEX sent_at_index;
  • 如果COUNT(DISTINCT message_id)> 4,000,000
    • 然后 ALTER TABLE s_relations_new DROP INDEX message_id_index;
  • 如果COUNT(DISTINCT target_object_id)> 4,000,000
    • 然后 ALTER TABLE s_relations_new DROP INDEX target_object_index;

一旦确定了这些索引的有用性或无用性,就可以重新加载数据

#
# Change the Column Name
# Load the Table
#
ALTER TABLE s_relations_new CHANGE sent_at sent_at_new int(11) DEFAULT NULL;
INSERT INTO s_relations_new SELECT * FROM s_relations;

就是这样吧?不 !!!

如果您的网站一直处于运行状态,则在加载s_relations_new期间可能有针对s_relations运行的INSERT。您如何检索那些丢失的行?

在s_relations_new中找到最大ID,然后在s_relations中的ID后面附加所有内容。为了确保该表被冻结并仅用于此更新,为了获得插入到s_relation_new中的最后几行,您必须有一点停机时间。这是您的工作:

在操作系统中,重新启动mysql,以便除root @ localhost外,其他任何人都无法登录(禁用TCP / IP):

$ service mysql restart --skip-networking

接下来,登录到mysql并加载最后几行:

mysql> SELECT MAX(id) INTO @maxidnew FROM s_relations_new;
mysql> INSERT INTO s_relations_new SELECT * FROM s_relations WHERE id > @maxidnew;
mysql> ALTER TABLE s_relations RENAME s_relations_old;
mysql> ALTER TABLE s_relations_new RENAME s_relations;

然后,正常重启mysql

$ service mysql restart

现在,如果您无法删除mysql,则必须对s_relations进行诱饵和切换。只需登录mysql并执行以下操作:

mysql> ALTER TABLE s_relations RENAME s_relations_old;
mysql> SELECT MAX(id) INTO @maxidnew FROM s_relations_new;
mysql> INSERT INTO s_relations_new SELECT * FROM s_relations_old WHERE id > @maxidnew;
mysql> ALTER TABLE s_relations_new RENAME s_relations;

试试看 !!!

CAVEAT:对此操作满意后,可以尽早将旧桌子放下:

mysql> DROP TABLE s_relations_old;

12

正确答案取决于您使用的MySQL引擎的版本。

如果使用5.6+,则重命名和添加/删除索引是在线执行的,即不复制表的所有数据。

ALTER TABLE照常使用,重命名和索引删除几乎立即生效,而索引添加则相当快(就像一次读取所有表一样快)。

如果使用5.1+,并且启用了InnoDB插件,则添加/删除索引也将在线。不确定重命名。

如果使用旧版本,ALTER TABLE它仍然是最快的,但是可能会非常慢,因为所有数据都将重新插入到后台的临时表中。

最后,是时候揭穿神话了。不幸的是,我在这里没有足够的业力来评论答案,但是我认为更正最投票的答案很重要。这是错误的

根据经验,如果所选列的基数大于表行计数的5%,则MySQL Query Optimizer将不使用索引

实际上是另一回事

索引对于选择行很有用,因此重要的是它们具有基数,这意味着许多不同的值和统计上具有相同值的几行。


链接到InnoDB插件文档(由于代表限制而无法粘贴)。
mezis 2013年

2
在MySQL 5.5上,我发现RENAME TABLE即时(按预期),但是CHANGE COLUMN重命名主键做了完整的复制……7个小时!可能仅因为它是主键?不好。
KCD 2013年

2

我对Maria DB 10.1.12遇到了同样的问题,但是在阅读了文档之后,我发现有一个选项可以执行“就地”操作,从而消除了表的副本。使用此选项,alter table非常快。以我为例:

alter table user add column (resettoken varchar(256),
  resettoken_date date, resettoken_count int), algorithm=inplace;

这非常快。没有算法选项,它将永远不会终止。

https://mariadb.com/kb/zh-CN/mariadb/alter-table/


0

对于列重命名,

ALTER TABLE tablename CHANGE columnname newcolumnname datatype;

应该很好并且不会造成任何停机时间。

对于索引,CREATE INDEX语句将锁定表。如前所述,如果它是一个未使用的奴隶,那不是问题。

另一种选择是创建一个具有适当列名和索引的全新表。然后,您可以将所有数据复制到其中,然后执行一系列

BEGIN TRAN;
ALTER TABLE RENAME tablename tablenameold;
ALTER TABLE RENAME newtablename tablename;
DROP TABLE tablenameold;
COMMIT TRAN;

这样可以最大程度地减少停机时间,但需要临时使用两倍的空间。


1
MySQL中的DDL不是事务性的。每个DDL语句都会触发COMMIT。我写的是这样的:dba.stackexchange.com/a/36799/877
RolandoMySQLDBA 2014年

0

我也遇到了这个问题,并使用了以下SQL:

/*on créé la table COPY SANS les nouveaux champs et SANS les FKs */
CREATE TABLE IF NOT EXISTS prestations_copy LIKE prestations;

/* on supprime les FKs de la table actuelle */
ALTER TABLE `prestations`
DROP FOREIGN KEY `fk_prestations_pres_promos`,
DROP FOREIGN KEY `fk_prestations_activites`;

/* on remet les FKs sur la table copy */
ALTER TABLE prestations_copy 
    ADD CONSTRAINT `fk_prestations_activites` FOREIGN KEY (`act_id`) REFERENCES `activites` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION,
    ADD CONSTRAINT `fk_prestations_pres_promos` FOREIGN KEY (`presp_id`) REFERENCES `pres_promos` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION;

/* On fait le transfert des données de la table actuelle vers la copy, ATTENTION: il faut le même nombre de colonnes */
INSERT INTO prestations_copy
SELECT * FROM prestations;

/* On modifie notre table copy de la façon que l'on souhaite */
ALTER TABLE `prestations_copy`
    ADD COLUMN `seo_mot_clef` VARCHAR(50) NULL;

/* on supprime la table actuelle et renome la copy avec le bon nom de table */
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE prestations;
RENAME TABLE prestations_copy TO prestations;
SET FOREIGN_KEY_CHECKS=1;   

我希望它可以帮助某人

问候,

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.