在使用表时更新表中数据而不锁定表的最佳方法是啥?

Posted

技术标签:

【中文标题】在使用表时更新表中数据而不锁定表的最佳方法是啥?【英文标题】:What's the best way to update data in a table while it's in use without locking the table?在使用表时更新表中数据而不锁定表的最佳方法是什么? 【发布时间】:2009-05-20 14:38:32 【问题描述】:

我在 SQL Server 2005 数据库中有一个经常使用的表。它有我们的产品可用性信息。我们每小时都会从我们的仓库获取更新,在过去的几年里,我们一直在运行一个截断表格并更新信息的例程。这只需要几秒钟,直到现在都不是问题。我们现在有更多的人使用我们的系统来查询这些信息,因此我们看到很多由于阻塞进程而导致的超时。

...所以...

我们研究了我们的选择,并提出了缓解问题的想法。

    我们会有两张桌子。表 A(活动)和表 B(非活动)。 我们将创建一个指向活动表(表 A)的视图。 现在所有需要此表信息的事物(4 个对象)都必须通过视图。 每小时例程会截断非活动表,使用最新信息对其进行更新,然后更新视图以指向非活动表,使其成为活动表。 此例程将确定哪个表处于活动状态,并基本上在它们之间切换视图。

这有什么问题?在查询中切换视图会导致问题吗?这能行吗?

感谢您的专业知识。

额外信息

例程是一个 SSIS 包,它执行许多步骤并最终截断/更新相关表

阻塞进程是查询此表的另外两个存储过程。

【问题讨论】:

如果您有许可证,两个独立的负载平衡服务器可以提供无缝的替代方案。您保持一个直播并更新另一个,然后切换。 【参考方案1】:

您是否考虑过使用snapshot isolation。它可以让你为你的 SSIS 东西开始一个大的交易,并且仍然从表中读取。

此解决方案似乎比切换表格更清洁。

【讨论】:

对此+1。这正是你应该做的。理想情况下,您将获得不需要转储所有内容的更新,但由于您不需要,因此诀窍是启动事务,删除(而不是截断)所有内容,然后加载所有内容。在你运行 commit 的那一刻,数据库将让一切都毫无间隙地改变。 为什么不截断?因为每次删除都会记录下来,删除会不会更慢? 我认为这就是重点。通过使用快照隔离,它的提交导致用户看到新数据。 truncate 没有记录,我不知道它会如何影响事务,但可能不是你想要的。事实上,我敢打赌,如果表有待处理的事务,截断会失败。 我们正在测试 Read_Committed_Snapshot 设置对我们的情况有何影响。到目前为止一切顺利。【参考方案2】:

我认为这是错误的做法 - 更新表必须锁定它,尽管您可以将锁定限制为每页甚至每行。

我会考虑不截断表格并重新填充它。这总是会干扰尝试阅读它的用户。

如果您确实更新而不是替换表格,您可以通过另一种方式控制 - 阅读用户不应阻塞表格,并且可能能够摆脱乐观阅读。

尝试将 with(nolock) 提示添加到读取 SQL 视图语句中。即使表格定期更新,您也应该能够吸引大量用户阅读。

【讨论】:

据我了解,SQl 提示,尤其是 NOLOCK,由于多种原因而不好。 tinyurl.com/qwloxh 它们远没有创建数据副本和重建视图以避免锁定那样“糟糕”。最好的方法是设置锁定实际上是在事务属性中,但这在这里是不可能的,因为您使用的是截断。我也不同意那篇文章中的观点 - 有时 SQL 提示是处理死锁的最佳方法,但您必须了解您在使用它们做什么。 NOLOCK 在某些情况下是完美的。你只需要知道它在做什么。 nolock 在这种情况下意味着有时(每当 ssis 包运行时)东西会完全发疯。查询将返回 0 个结果(截断后) 查询将返回错误数据(大事务的中间时间) '完全疯了'?这是一个技术术语吗? ;-) 是的 - nolock 会导致脏读,但保证不会锁定。保证干净读取的唯一方法是锁定表,这就是导致问题的原因。对于大多数应用程序来说,偶尔的脏读确实不是问题,因此 nolock 可以成为一个非常有用的工具。您不会在财务或数据关键型应用程序中使用它,但您也不应该在此类应用程序中使用截断或未处理的操作。【参考方案3】:

就个人而言,如果您总是要引入停机时间来对表运行批处理,我认为您应该在业务/数据访问层管理用户体验。引入一个表管理对象,该对象监视与该表的连接并控制批处理。

当新的批处理数据准备好时,管理对象会停止所有新的查询请求(甚至排队?),允许现有查询完成,运行批处理,然后重新打开表进行查询。管理对象可以引发 UI 层可以解释的事件 (BatchProcessingEvent) 以让人们知道该表当前不可用。

我的 0.02 美元,

内特

【讨论】:

我们已经考虑过这个选项,我们真的很喜欢这个选项,但是它需要做更多的工作才能开始,而且时间是我们没有的奢侈品。不幸的是。但我同意不管服务器批处理是最佳的处理这个。【参考方案4】:

刚刚读到你正在使用 SSIS

您可以使用来自:http://www.sqlbi.eu/Home/tabid/36/ctl/Details/mid/374/ItemID/0/Default.aspx 的 TableDiference 组件

(来源:sqlbi.eu)

通过这种方式,您可以一个接一个地将更改应用到表,但是当然,这会慢得多,并且根据表的大小,服务器上需要更多的 RAM,但锁定问题将被完全纠正。

【讨论】:

【参考方案5】:

为什么不使用事务来更新信息而不是截断操作。

截断未记录,因此无法在事务中完成。

如果您的操作是在事务中完成的,那么现有用户不会受到影响。

如何做到这一点取决于表格的大小以及数据变化的剧烈程度等因素。如果您提供更多详细信息,也许我可以提供更多建议。

【讨论】:

例程是一个 SSIS 包,看起来整个事情都在事务中运行,这就是导致阻塞的原因。【参考方案6】:

一种可能的解决方案是尽量减少更新表所需的时间。

我会先创建一个临时表来从仓库中下载数据。

如果你必须在决赛桌中进行“插入、更新和删除”

假设决赛桌是这样的:

Table Products:
    ProductId       int
    QuantityOnHand  Int

您需要从仓库更新 QuantityOnHand。

首先创建一个临时表,如:

Table Prodcuts_WareHouse
    ProductId       int
    QuantityOnHand  Int

然后像这样创建一个“Actions”表:

Table Prodcuts_Actions
    ProductId       int
    QuantityOnHand  Int
    Action          Char(1)

更新过程应该是这样的:

1.截断表Prodcuts_WareHouse

2.截断表Prodcuts_Actions

3.用仓库中的数据填充 Prodcuts_WareHouse 表

4. 用这个填充 Prodcuts_Actions 表:

插入:

INSERT INTO Prodcuts_Actions (ProductId, QuantityOnHand,Action)
SELECT     SRC.ProductId, SRC.QuantityOnHand, 'I' AS ACTION
FROM         Prodcuts_WareHouse AS SRC LEFT OUTER JOIN
                      Products AS DEST ON SRC.ProductId = DEST.ProductId
WHERE     (DEST.ProductId IS NULL)

删除

INSERT INTO Prodcuts_Actions (ProductId, QuantityOnHand,Action)
SELECT     DEST.ProductId, DEST.QuantityOnHand, 'D' AS Action
FROM         Prodcuts_WareHouse AS SRC RIGHT OUTER JOIN
                      Products AS DEST ON SRC.ProductId = DEST.ProductId
WHERE     (SRC.ProductId IS NULL)

更新

INSERT INTO Prodcuts_Actions (ProductId, QuantityOnHand,Action)
SELECT     SRC.ProductId, SRC.QuantityOnHand, 'U' AS Action
FROM         Prodcuts_WareHouse AS SRC INNER JOIN
                      Products AS DEST ON SRC.ProductId = DEST.ProductId AND SRC.QuantityOnHand <> DEST.QuantityOnHand

到目前为止,您还没有锁定决赛桌。

5.在事务中更新最终表:

BEGIN TRANS

DELETE Products FROM Products INNER JOIN
Prodcuts_Actions ON Products.ProductId = Prodcuts_Actions.ProductId
WHERE     (Prodcuts_Actions.Action = 'D')

INSERT INTO Prodcuts (ProductId, QuantityOnHand)
SELECT ProductId, QuantityOnHand FROM Prodcuts_Actions WHERE Action ='I';

UPDATE Products SET QuantityOnHand = SRC.QuantityOnHand 
FROM         Products INNER JOIN
Prodcuts_Actions AS SRC ON Products.ProductId = SRC.ProductId
WHERE     (SRC.Action = 'U')

COMMIT TRAN

通过上述所有过程,您可以将要更新的记录数量降至最低,因此更新时最终表将被锁定的时间。

你甚至可以在最后一步不使用事务,所以在命令之间表会被释放。

【讨论】:

【参考方案7】:

如果您拥有 SQL Server 企业版,那么我建议您使用 SQL Server 分区技术。

您可以将当前所需的数据保存在“实时”分区中,并将更新版本的数据保存在“辅助”分区中(不可用于查询,而是用于管理数据)。

将数据导入“辅助”分区后,您可以立即将“实时”分区 OUT 和“辅助”分区 IN 切换,从而实现零停机且无阻塞。

完成切换后,您可以截断不再需要的数据,而不会影响新活动数据(以前是辅助分区)的用户。

每次您需要执行导入作业时,只需重复/反转该过程即可。

要了解有关 SQL Server 分区的更多信息,请参阅:

http://msdn.microsoft.com/en-us/library/ms345146(SQL.90).aspx

或者你可以问我:-)

编辑:

附带说明,为了解决任何阻塞问题,您可以使用 SQL Server 行版本控制技术。

http://msdn.microsoft.com/en-us/library/ms345124(SQL.90).aspx

【讨论】:

这是我们想要用视图完成的想法,但我们只有标准版的 SQL Server。【参考方案8】:

我们在我们的高使用率系统上执行此操作,并且没有遇到任何问题。但是,与所有事物数据库一样,确保它会有所帮助的唯一方法是在 dev 中进行更改,然后对其进行负载测试。不知道您的 SSIS 包还有其他用途,它仍然可能导致阻塞。

【讨论】:

【参考方案9】:

如果表不是很大,您可以在应用程序中缓存数据一小段时间。它可能不会完全消除阻塞,但会减少发生更新时查询表的机会。

【讨论】:

【参考方案10】:

也许对阻塞的进程进行一些分析是有意义的,因为它们似乎是您的环境中发生变化的一部分。只需要一个写得不好的查询来创建您所看到的块。除非查询写得不好,否则该表可能需要一个或多个覆盖索引来加速这些查询并让您重新开始工作,而无需重新设计您已经工作的代码。

希望这会有所帮助,

比尔

【讨论】:

以上是关于在使用表时更新表中数据而不锁定表的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

请教一下mysql 行锁命令是啥?

在一个视图上使用不同数据填充多个表的最佳方法是啥? [关闭]

更新 MARA 表的最佳解决方案是啥?

更新闭包表的最佳方法是啥?

使用 Sqoop 并行导入 Oracle 表的最佳方法是啥?

如何在 postgres 中使用“更新跳过锁定”而不锁定查询中使用的所有表中的行?