在没有表锁定的情况下在巨大的MySQL生产表上创建索引


104

我需要在约500万行的MySQL表上创建索引。这是一个生产表,如果我运行CREATE INDEX语句,我担心所有内容都会完整...

有没有一种方法可以创建该索引而不阻塞插入和选择?

只是想知道我没有停止,创建索引并重新启动系统!


1
确保您的myisam_sort_buffer_size和myisam_max_sort_file_size足够大。
乔恩·布莱克

Answers:


130

[2017]更新:MySQL 5.6支持在线索引更新

https://dev.mysql.com/doc/refman/8.0/zh-CN/innodb-online-ddl-operations.html#online-ddl-index-syntax-notes

在MySQL 5.6和更高版本中,在创建或删除索引时,该表仍可用于读取和写入操作。CREATE INDEX或DROP INDEX语句仅在访问表的所有事务完成后才完成,因此索引的初始状态反映了表的最新内容。以前,在创建或删除索引时修改表通常会导致死锁,从而取消表上的INSERT,UPDATE或DELETE语句。

[2015]在MySQL 5.5中更新表索引块写入

从上面的答案:

“如果在数据库联机时使用的版本大于5.1索引,则创建该版本。因此不必担心不会中断生产系统的使用。”

这是**** FALSE ****(至少对于MyISAM / InnoDB表而言,这是在那里使用的99.999%的人使用的功能。群集版则有所不同。)

在表上执行更新操作将BLOCK正在创建索引时。MySQL对此确实(以及其他一些东西)非常愚蠢。

测试脚本:

(   
  for n in {1..50}; do
    #(time mysql -uroot -e 'select  * from website_development.users where id = 41225\G'>/dev/null) 2>&1 | grep real;
    (time mysql -uroot -e 'update website_development.users set bio="" where id = 41225\G'>/dev/null) 2>&1 | grep real;
  done
) | cat -n &
PID=$!
sleep 0.05
echo "Index Update - START"
mysql -uroot website_development -e 'alter table users add index ddopsonfu (last_name, email, first_name, confirmation_token, current_sign_in_ip);'
echo "Index Update - FINISH"
sleep 0.05
kill $PID
time mysql -uroot website_development -e 'drop index ddopsonfu on users;'

我的服务器(InnoDB):

Server version: 5.5.25a Source distribution

输出(注意第六个操作如何阻塞约400ms来完成索引更新):

 1  real    0m0.009s
 2  real    0m0.009s
 3  real    0m0.009s
 4  real    0m0.012s
 5  real    0m0.009s
Index Update - START
Index Update - FINISH
 6  real    0m0.388s
 7  real    0m0.009s
 8  real    0m0.009s
 9  real    0m0.009s
10  real    0m0.009s
11  real    0m0.009s

与不阻塞的读取操作(交换脚本中的行注释):

 1  real    0m0.010s
 2  real    0m0.009s
 3  real    0m0.009s
 4  real    0m0.010s
 5  real    0m0.009s
Index Update - START
 6  real    0m0.010s
 7  real    0m0.010s
 8  real    0m0.011s
 9  real    0m0.010s
...
41  real    0m0.009s
42  real    0m0.010s
43  real    0m0.009s
Index Update - FINISH
44  real    0m0.012s
45  real    0m0.009s
46  real    0m0.009s
47  real    0m0.010s
48  real    0m0.009s

更新MySQL的架构而无需停机

到目前为止,我所知道的只有一种方法可以更新MySql模式,而不会出现可用性中断的情况。通函大师:

  • 主机A上正在运行您的MySQL数据库
  • 使主控B投入使用,并复制主控A的写入(B是A的从属)
  • 在母版B上执行架构更新。它将在升级过程中落后
  • 让B师傅赶上。不变的:您的架构更改必须能够处理从降级架构复制的命令。索引更改符合条件。简单的列添加通常符合条件。删除列?可能不是。
  • 以自动方式将所有客户端从主机A交换到主机B。如果您想安全(请相信我),则应确保对A的最后一次写入被复制到B 之前B第一次写入。如果允许并发写入2个以上的主服务器,则...您最好在DEEP级别上了解MySQL复制,否则您将面临痛苦。极度疼痛。像,您是否有一个列是AUTOINCREMENT?您很困惑(除非您在一个母版上使用偶数,在另一个母版上使用奇数)。不要相信MySQL复制可以“做正确的事”。它不是很聪明,不会挽救您。与从命令行复制二进制事务日志并手动重播它们相比,安全性稍差一些。尽管如此,断开所有客户端与旧主服务器的连接并将它们切换到新主服务器仍可以在几秒钟内完成,这比等待数小时的架构升级要快得多。
  • 现在,师父B是您的新主人。您有了新的架构。生活很好。喝啤酒;最坏的过去了。
  • 与主服务器A重复此过程,升级其架构,以便他成为您的新辅助主服务器,并准备在您的主服务器(现在为主服务器B)断电或刚起手而死时接任您。

不是一种简单的更新架构的方法。在恶劣的生产环境中可行;是的。拜托,拜托,拜托,如果有一种更简单的方法可以在不阻止写入的情况下向MySQL表添加索引,请告诉我。

谷歌搜索使我引向这篇文章,它描述了类似的技术。更好的是,他们建议在手术过程中的同一时间喝酒(请注意,在阅读本文之前,我已经写了答案)!

Percona的pt在线模式更改

我上面链接的文章讨论了一种工具pt-online-schema-change,其工作方式如下:

  • 创建具有与原始结构相同的新表。
  • 在新表上更新架构。
  • 在原始表上添加一个触发器,以便使更改与副本保持同步
  • 从原始表中批量复制行。
  • 将原始表移开,并替换为新表。
  • 放下旧桌子。

我从未亲自尝试过该工具。青年汽车

RDS

我目前正在通过Amazon的RDS使用MySQL 。这是一个非常漂亮的服务,用于包装和管理MySQL,使您可以通过一个按钮添加新的只读副本,并跨硬件SKU透明地升级数据库。真的很方便 您没有对数据库的超级访问权限,因此无法直接破坏复制(这是福还是祸?)。但是,您可以使用只读副本升级在只读从属服务器上更改架构,然后将其升级为新的主服务器。与我上面描述的技巧完全相同,但执行起来却非常容易。他们仍然没有为您提供帮助。您必须重新配置并重新启动您的应用程序。


3
pt-online-schema-change即使在主从复制中也能很好地工作。我已使用它在生产主数据库上的忙于读取20M +记录的表上进行实时迁移,该表具有2个复制从属,不会造成任何打or或停机。准备脚本需要花费一些时间,而且我通常必须创建一个包含原始SQL更改的.sql文件和一个作为包装程序的.sh文件,以运行相同的SQL,但使用片段格式(没有ALTER TABLE)。您可以使用pt-online-schema-change来运行多个命令,方法是将它们串起来并以逗号分隔。
Alex Le

-1; 我不知道较旧的版本,但是我知道索引创建不会阻止MySQL 5.6+中的并发DML(在编写此答案时已存在RC,并且在此答案持续时已正式发布RC)于2013年5月编辑),因为我依靠它在生产表上运行多个小时的索引创建,同时仍然接受插入。尽管您可能对在5.5及以下版本中阻止DML的索引创建是正确的,但此处演示的不到一秒的延迟并不完全令人信服。
Mark Amery

@MarkAmery-阻止行为就是阻止行为,并且400ms是永恒的。MySQL 5.5块用于索引更新。建立一个更大的测试数据库,它将阻塞数秒,数小时或数天。我在MySQL 5.6进行在线模式更新之前写了这篇文章,所以我的原始内容没有反映出这一事实。我已经更新了帖子以反映新的可用信息。
戴夫·多普森

@DaveDopson,您是否100%确定仅UPDATE操作被阻止?
toto_tico

我测试的版本就是这种情况。
戴夫多普森

67

如本博文所述,InnoDB ALTER TABLE机制已针对MySQL 5.6进行了完全重新设计。

(有关此主题的独家概述,MySQL文档可以提供一个下午的阅读时间。)

要将索引添加到表中而不会UPDATE/上导致锁定INSERT,可以使用以下语句格式:

ALTER TABLE my_table ADD INDEX my_table__idx (my_column), ALGORITHM=INPLACE, LOCK=NONE;


16

MySQL 5.6更新(2013年2月):现在,即使在使用InnoDB表创建索引时,您也可以执行读写操作-http: //dev.mysql.com/doc/refman/5.6/en/innodb-create-index -overview.html

在MySQL 5.6和更高版本中,在创建或删除索引时,该表仍可用于读取和写入操作。CREATE INDEX或DROP INDEX语句仅在访问表的所有事务完成后才完成,因此索引的初始状态反映了表的最新内容。以前,在创建或删除索引时修改表通常会导致死锁,从而取消表上的INSERT,UPDATE或DELETE语句。

和:

在MySQL 5.6中,此功能变得更加通用:您可以在创建索引的同时对表进行读写,并且可以执行多种ALTER TABLE操作,而无需复制表,不阻塞DML操作或同时执行这两种操作。因此,在MySQL 5.6和更高版本中,我们通常将此功能集称为在线DDL,而不是快速索引创建。

来自http://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_fast_index_creation


那如何解释戴夫的分析呢?
Nikhil Sahu

1
@NikhilSahu Dave显然不是在MySQL 5.6上进行测试,而是在某些旧版本上进行测试。请注意,在Dave发布其答案的初始修订版时,尚未发布5.6。
Mark Amery

+1。我的分析基于MySQL 5.5(2013年提供的最新版本)。我正在更新我的答案,以反映MySQL 5.6中的新功能。
Dave Dopson

3

如果您确实要确保迁移不会导致站点崩溃,那么可以进行pt-online-schema-change。

正如我在上述评论中所写的那样,我在生产中进行pt-online-schema-change方面有一些经验。我们有20M +条记录的主表和一个主目录-> 2个只读复制从属目录。从添加新列,更改字符集到添加多个索引,我至少完成了pt-online-schema-change的数十次迁移。在迁移期间,我们也为大量的流量提供服务,而且我们没有遇到任何麻烦。当然,在生产上运行之前,您必须非常彻底地测试所有脚本。

我试图将更改批量添加到1个脚本中,以便pt-online-schema-change只需要复制一次数据。并且更改列名时要非常小心,因为这会丢失数据。但是,添加索引应该没问题。


我不同意您对的无条件推荐pt-online-schema-change。很好,但是在许多情况下,MySQL 5.6+的在线DDL功能已经可以正常工作了,这是过分的。它还有局限性(例如不能很好地使用触发器),并且在进行模式更改时,每次插入原始表所需的写入量加倍。与普通的在线模式更改相比,它将给您的磁盘增加更多的负担,因此,在仅运行模式更改即可正常运行的情况下,就有可能“破坏您的站点”。
Mark Amery

我是根据当时对pt-online-schema-change的实际经验撰写的,因此我不确定为什么您会将我的建议称为“不合格”。在我运行架构更改的任何给定时刻,我们至少有1000多名访问者在站点上,当然,磁盘IO正在增加负担,但是我们的站点并未关闭。良好的缓存也有帮助。我没有使用MySQL 5.6+在线DDL,但是根据我的经验,pt-online-schema-change在我们的案例中做得很好。
Alex Le

1
@AlexYe Yikes,我的意思是“毫无保留”的意思是“不合格”,而不是“没有资格发表评论的人提供的意思”-后一种解释直到我看到您的评论才出现,我当然没有不是我想要的!也就是说,我说的是虽然pt-online-schema-change是有用的工具,但在许多情况下,普通的在线DDL既好又少数,它更好。因此,对它的任何建议都应谨慎考虑,而不是通用。
Mark Amery
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.