ALTER TABLE没有锁定表?


107

当在MySQL中执行ALTER TABLE语句时,整个表在语句期间被锁定(允许并发读取,但禁止并发写入)。如果它是一个大表,则可能会在很长一段时间内阻止INSERT或UPDATE语句。有没有一种方法可以进行“热更改”,例如以在整个过程中仍可更新表的方式添加列?

通常,我对MySQL解决方案感兴趣,但如果MySQL无法做到,我也会对其他RDBMS感兴趣。

澄清一下,我的目的仅仅是避免在需要额外表列的新功能投入生产时避免停机。任何数据库架构都会随着时间改变,这是生活中的事实。我不明白为什么我们应该接受这些变化不可避免地导致停机的原因。那太弱了。


2
必须想知道您将更改表多少次?
Allain Lalonde

1
恕我直言,数据库架构更改与全新版本相关联-它们不会像其他更改那样零星地推出。这不可避免。
dkretz

9
@AllainLalonde-超过0次使此问题成为合法问题,尤其是如果系统中的停机时间会导致生命或金钱损失的话。无论如何,有时确实会出现新的软件要求。
内森·朗

Answers:


60

唯一的其他选择是手动执行许多RDBMS系统要做的事情...-
创建一个新表

然后,您可以一次复制一个块中的旧表的内容。尽管始终对源表上的任何INSERT / UPDATE / DELETE都保持谨慎。(可以通过触发器进行管理。尽管这会导致速度变慢,但这不是锁...)

完成后,更改源表的名称,然后更改新表的名称。最好是在交易中。

完成后,重新编译使用该表的所有存储过程等。执行计划可能将不再有效。

编辑:

关于此限制有点差的一些评论。所以我想我应该对它放一个新的角度来说明为什么它是如此...

  • 添加新字段就像在每一行上更改一个字段一样。
  • 字段锁比行锁要难得多,不用管表锁。

  • 您实际上是在更改磁盘的物理结构,每条记录都会移动。
  • 这的确像是对整个表的更新,但影响更大……

2
并在交换之前制定周全的测试计划。如果失败,请重新开始。
dkretz

2
通过触发器管理同步是一个好主意。我使用MySQL已有很长时间了,以至于我现在忘记了它们具有触发器。我已经使用了这种技术,现在有了功能正常的热更改脚本。带进度条。它与MyISAM一起使用。生活很好。
丹尼尔(Daniel)2009年

2
+1这实际上是当您在UI中进行某些类型的表更改时,SQL Enterprise Manager在后台执行的操作。在SQL 2008中,他们实际上添加了一个警告,以便用户知道其正在执行此剧烈动作。
BradC

2
您没有提到有关引用正在更改的表的外键的任何信息。那不是问题吗?
拉斐

2
@MohammadRafayAleem-以及AUTOINCREMENT字段,视图,触发器等,但是即使如此,该方法仍然可行。
MatBailie

42

Percona制作了一个名为pt-online-schema-change的工具,可以完成此操作。

它实质上是复制表并修改新表。为了使新表与原始表保持同步,它使用触发器进行更新。这样就可以在后台准备新表的同时访问原始表。

这类似于上面的Dems建议的方法,但是这样做是自动的。

他们的某些工具具有学习曲线,即连接到数据库,但是一旦遇到问题,它们就是很棒的工具。

例如:

pt-online-schema-change --alter "ADD COLUMN c1 INT" D=db,t=numbers_are_friends

似乎链接已断开。我发现链接有效。
Noam Ben Ari

25

这个问题来自2009年。现在MySQL提供了一个解决方案:

在线DDL(数据定义语言)

在DDL(主要是ALTER TABLE)操作过程中提高InnoDB表的性能,并发性和可用性的功能。有关详细信息,请参见第14.11节“ InnoDB和在线DDL”。

具体内容因操作类型而异。在某些情况下,可以在ALTER TABLE进行时同时修改表。可能无需执行表副本或使用特殊优化的表副本类型就可以执行该操作。空间使用情况由innodb_online_alter_log_max_size配置选项控制。

通过选择是完全阻止对表的访问(LOCK = EXCLUSIVE子句),允许查询而不是DML(LOCK = SHARED子句)还是允许完全查询和DML,它使您可以在DDL操作期间调整性能和并发性之间的平衡。访问表(LOCK = NONE子句)。当省略LOCK子句或指定LOCK = DEFAULT时,MySQL会根据操作类型允许尽可能多的并发。

在可能的情况下就地执行更改,而不是创建表的新副本,可以避免与复制表和重建二级索引相关的磁盘空间使用和I / O开销的暂时增加。

有关更多信息,请参见MySQL 5.6参考手册-> InnoDB和Online DDL

似乎在线DDL在MariaDB中也可用

或者,您可以使用ALTER ONLINE TABLE来确保您的ALTER TABLE不会阻止并发操作(不带锁)。它等效于LOCK = NONE。

关于ALTER TABLE的MariaDB KB


3
遗憾的是,除了投票之外,别无选择,只能将其浮动到顶部,因为它几乎完全否定了所有其他答案,因为它们不再引用MySQL的当前版本。
Burhan Ali,


14

如果可以的话,我建议使用Postgres。使用postgres,通过以下过程基本上不会造成停机:

另一个很棒的功能是大多数DDL语句都是事务性的,因此您可以在SQL事务中进行整个迁移,如果出现问题,则整个过程都会回滚。

我之前写过这篇文章,也许它可以使您对其他优点有更多的了解。


6
Postgres仍然在alter上创建排他锁,以防止其他人从该表中读取。
clofresh 2011年

5
我不同意“基本上没有停机时间”的说法。正如clofresh所说,ALTER TABLE会在表上获取排他锁,从而阻止所有并发的读写操作。以我的经验,对于活动表,大多数时候您甚至都不会得到锁(ALTER TABLE会饿死)。如果不十分小心,使用事务处理就很容易陷入僵局。因此,我现在总是在更改Postgres中的现有表时设置停机时间。
Pankrat 2012年

1
更详细的说明:dba.stackexchange.com/questions/27153/…它提到了排他锁的含义以及一些解决方法
John Douthat

4
是的,在postgres中更改表会获得排他锁,但是由于操作本身会在毫秒内完成,因此在大多数情况下这实际上是无关紧要的。我在工作日中亲自将列添加到亿行表中,导致停机时间为零。
Noah Yetter 2014年

2
@cobbzilla是的,DROP COLUMN一样快。引擎盖下的主要作用是将列标记为隐藏。删除之前该列中存在的值仍保留在数据文件中(并且对其他事务可见),除非您进行VACUUM FULL,否则这些值将保持不变。
Noah Yetter

7

由于您询问了其他数据库,因此这里是有关Oracle的一些信息。

将NULL列添加到Oracle表是一种非常快速的操作,因为它仅更新数据字典。这会在很短的时间内在表上保持排他锁。但是,它将使所有有害的存储过程,视图,触发器等无效。这些将自动重新编译。

如果需要,可以从那里使用ONLINE子句创建索引。同样,只有非常短的数据字典锁。它会读取整个表以查找要索引的内容,但不会在执行此操作时阻止任何人。

如果需要添加外键,则可以执行此操作,并使Oracle信任您数据正确。否则,它需要读取整个表并验证所有可能很慢的值(首先创建索引)。

如果需要在新列的每一行中输入默认值或计算值,则需要运行大量更新或运行一个小的实用程序来填充新数据。这可能会很慢,尤其是如果这些行变得更大并且不再适合它们的块时。可以在此过程中管理锁定。由于您的应用程序的旧版本仍在运行,而该版本仍不知道此列,因此您可能需要偷偷摸摸的触发器或指定默认值。

从那里,您可以在应用程序服务器上切换到新版本的代码,它将继续运行。放下偷偷摸摸的扳机。

另外,您可以使用DBMS_REDEFINITION,这是设计用于执行此类操作的黑匣子。

所有这些都非常麻烦测试,以至于每当发布主要版本时,我们都将在周日早间断电。


3

如果在进行应用程序更新时无法承受数据库的停机时间,则应考虑维护两节点群集以实现高可用性。通过简单的复制设置,您可以进行几乎完全在线的结​​构更改,如您建议的那样:

  • 等待所有更改复制到被动从站上
  • 将被动从站更改为主动主站
  • 对老主人进行结构调整
  • 将更改从新的母版复制回旧的母版
  • 再次进行主交换和新应用部署

它并不总是那么容易,但是它通常可以在停机时间为0时起作用!第二个节点不必只是被动节点,它可以用于测试,进行统计或作为备用节点。如果没有基础架构,则可以在单台计算机(带有两个MySQL实例)中设置复制。


1
旧的主机是在群集之外还是在群集内?
John Chornelius

2

不。如果您使用的是MyISAM表,就我所知,它们仅执行表锁-没有记录锁,它们只是尝试通过简单性使所有内容保持超快状态。(其他MySQL表的操作有所不同。)在任何情况下,您都可以将表复制到另一个表,对其进行更改,然后切换它们,以进行差异更新。

这是一个巨大的改变,我怀疑任何DBMS都会支持它。首先可以对表中的数据进行处理被认为是一种好处。



是的,MySQL是畸变。这就是为什么我要专门讨论“标准”表。
dkretz

您写的-标准MySQL表仅做表锁-这是不正确的。
Eran Galperin

您如何从引用页面解释有关MyISAM(即MySQL标准)表的信息?“ MySQL对MyISAM和MEMORY表使用表级锁定,对BDB表使用页级锁定,对InnoDB表使用行级锁定。”
dkretz

一些存储引擎使用行级锁定,而一些使用表级锁定。没有标准的存储引擎(也许您是phpMyAdmin中的默认设置...)
Eran Galperin

2

临时解决方案...

其他解决方案可能是,使用原始表的主键添加另一个表以及新列。

将主键填充到新表上,并在新表中填充新列的值,然后修改查询以将该表联接以进行选择操作,并且还需要单独插入,更新该列值。

当您可以停机时,可以更改原始表,修改DML查询并删除先前创建的新表

否则,您可能会从percona寻求集群方法,复制和pt-online-schema工具


1

使用Innodb插件,可以“快速”完成仅添加或删除二级索引的ALTER TABLE语句,即无需重建表。

但是,一般来讲,在MySQL中,任何ALTER TABLE都涉及重建整个表,这可能需要很长时间(即,如果表中包含有用的数据量)。

您确实需要设计您的应用程序,以便不需要定期执行ALTER TABLE语句。您当然不希望在应用程序正常运行期间完成任何ALTER TABLE,除非您准备等待或要更改小表。


1

我建议使用以下两种方法之一:

  1. 设计数据库表时要考虑到潜在的变化。例如,我曾与Content Management Systems合作,后者会定期更改内容中的数据字段。与其构建物理数据库结构来满足初始CMS字段需求,不如构建一个灵活的结构,这要好得多。在这种情况下,请使用Blob文本字段(例如varchar(max))保存灵活的XML数据。这使得结构变更的频率降低了。结构变更的成本可能很高,因此这里的成本也有好处。

  2. 有系统维护时间。系统在更改期间(每月等)都会脱机,并且更改会安排在一天中流量最小的时间(例如3-5am)进行。更改是在生产开始之前分阶段进行的,因此您将获得良好的停机时间固定窗口估计。

2a。具有冗余服务器,这样,当系统出现故障时,整个站点都不会宕机。这样一来,您就可以以交错方式“滚动”更新,而无需关闭整个站点。

选项2和2a可能不可行;它们往往仅适用于较大的站点/运营。但是,它们是有效的选项,我个人使用了此处介绍的所有选项。


1

如果有人仍在阅读或碰巧来到这里,这就是使用像mongodb这样的NoSQL数据库系统的最大好处。在更改表以添加其他功能的列或在具有数百万行和高写入量的大表上建立索引时,我遇到了同样的问题。它最终将锁定很长时间,因此在LIVE数据库上执行此操作会使我们的用户感到沮丧。在小桌子上,您可以摆脱它。

我讨厌这样的事实,我们必须“设计表以避免更改它们”。我只是认为在当今的网站世界中不行。您无法预测人们将如何使用您的软件,这就是您根据用户反馈快速更改事物的原因。使用mongodb,您可以在不停机的情况下随意添加“列”。您甚至根本没有添加它们,只需插入带有新列的数据,然后它就会自动执行。

值得一试:www.mongodb.com


2
MySQL仍在许多系统中使用,所以问题实际上是关于如何在SQL RDBMS中实现模式更改,即使我也是NoSQL的热心支持者。
Alexy

1

通常,答案将是“否”。您正在更改表的结构,这可能需要进行大量更新”,我绝对同意。如果您希望经常进行此操作,那么我将提供“虚拟”列的替代方法-使用VIEWs代替为表SELECT荷兰国际集团的数据。IIRC,改变视图的定义是相对较轻的,当查询计划被编译通过视图的间接完成。该费用是你必须将列添加到一个新表,使JOIN在该列中查看。

当然,这仅在您可以使用外键执行级联删除和诸如此类的操作时才有效。另一个好处是您可以创建一个包含数据组合的新表,并将视图指向该表,而不会影响客户端的使用。

只是一个想法。


1

在这方面,Postgres和MySQL之间的区别在于,在Postgres中,它不重新创建表,而是修改数据字典,这与Oracle类似。因此,该操作速度很快,但仍然需要像其他人所说的那样在很短的时间内分配一个排他的DDL表锁。

在MySQL中,该操作将在阻止事务的同时将数据复制到新表中,这是5.6之前的MySQL DBA的主要难题。

好消息是,自从MySQL 5.6版本发布以来,该限制已基本解除,您现在可以享受MYSQL DB的真正威力。


3
似乎您正在尝试链接到有关MySql 5.6中更改的参考,但此操作无效。请再试一遍。
dg99



0

如果可以预测虚拟列的类型(并使它们为可空),则虚拟列是一个好主意。检查您的存储引擎如何处理空值。

如果您甚至在通过电话在机场时提及一个表名,MyISAM都会锁定所有内容。就是那样...

话虽这么说,锁并不是什么大不了的事。只要您不尝试将新列的默认值添加到每一行,而应将其设置为null,并且您的存储引擎足够聪明以至于不能编写它,则应该只使用一个锁保持足够长的时间以更新元数据。如果您确实尝试编写新的值,那么您就敬酒了。


1
我尝试将NULL列添加到InnoDB表中,并且它必须重建整个表。不是简单的“更新元数据”操作。
丹尼尔(Daniel)2009年

我认为这种想法是在设计时在数据库中包括额外的可为空的列,这样,如果需要一项新功能,只需开始使用它就可以“添加”新列。它的名字不太好,但是如果正确选择/预测了数据类型,它应该可以工作。
超级猫

0

TokuDB可以添加/删除列并“热”添加索引,该表在整个过程中完全可用。可通过www.tokutek.com获得


-6

并不是的。

毕竟,您正在更改表的基础结构,而其中的一些信息对于基础系统非常重要。您还可能在磁盘上移动大量数据。

如果您打算做很多事情,最好不要在表上填充“虚拟”列,以备将来使用。


3
用虚拟列填充表似乎是个坏主意。
2012年
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.