是否可以锁定 SQL 服务器中的列值?
Is it possible to lock on a value of a column in SQL Server?
我有一个 table 看起来像这样:
Id GroupId
1 G1
2 G1
3 G2
4 G2
5 G2
应该可以随时读取所有行(仅提交)。当有更新时,我想要一个锁定组 ID 的事务,即在任何给定时间应该只有一个事务尝试更新每个 GroupId。
理想情况下应该仍然可以读取所有提交的行(即其他 transaction/ordinary 不会尝试获取“更新每组锁”的读取应该仍然能够读取)。
我想这样做的原因是更新不能依赖“过时”的数据。 IE。我确实在事务中进行了一些计算,而另一个事务无法编辑 ID 为 1 的行,也无法在第一个事务读取这些行后添加具有相同 GroupId 的新行(即使第一个事务永远不会修改行本身,它将是取决于它的价值)。
另一个“很高兴”的要求是有时我会需要相同的要求“跨组”,即更新事务必须同时锁定 2 个组。 (这不是组的动态数量,而只是 2 个)
这里有一些想法。我认为它们中的任何一个都不完美 - 我认为您需要给自己一套 use-cases 并尝试它们。我在应用锁后尝试的一些情况
- 使用 WHERE 筛选器选择另一个组
- 使用 WHERE 过滤器作为锁定组的 SELECTs
- 更新 table 将 WHERE 子句作为另一个组
- 关于 table 的更新,其中 ID(不是 GrpID!)未被锁定
- 在行被锁定的 table 更新(例如,ID 1 和 2)
- 使用那个 GrpId
插入 table
我有一种有趣的感觉,其中 none 个将是 100%,但最有可能的答案是第二个(设置事务隔离级别)。它可能会比预期锁定更多,但会为您提供所需的隔离。
还有一件事要记住:如果您锁定许多行(例如,有数千行具有您想要的 GrpId),那么 SQL 服务器可以将锁升级为 full-table 锁. (我相信临界点是 5000 锁,但不确定)。
Old-school 黑客工作
在交易开始时,以某种方式更新所有相关行,例如,
BEGIN TRAN
UPDATE YourTable
SET GrpId = GrpId
WHERE GrpId = N'G1';
-- Do other stuff
COMMIT TRAN;
没有其他东西可以使用它们,因为(太棒了!)它们是事务中的写入。
方便-设置隔离级别
在您的事务之前,将隔离级别设置得较高,例如 SERIALIZABLE。
您可能希望在事务开始时读取所有相关行(例如,SELECT Grp FROM YourTable WHERE Grp = N'Grp1'
)以锁定它们不被更新。
灵活但需要大量编码
使用 sp_getapplock and sp_releaseapplock 的资源锁定。
这些用于锁定 资源 ,而不是 table 或行。
什么是资源?好吧,任何你想要的。在这种情况下,我建议 'Grp1'、'Grp2' 等。它实际上并不锁定行。相反,您询问(通过 sp_getapplock 或 APPLOCK_TEST)您是否可以获得资源锁。如果是这样,请继续。如果没有,那就停止。
任何引用这些 table 的代码都需要审查并可能进行修改,以询问是否允许 运行。如果某事不请求许可而直接执行,则没有实际的真正锁阻止它(除非通过您明确指定的任何交易)。
您还需要确保正确处理错误(例如,仍然释放 app_lock)并且被阻止的进程是 re-tried.
我有一个 table 看起来像这样:
Id GroupId
1 G1
2 G1
3 G2
4 G2
5 G2
应该可以随时读取所有行(仅提交)。当有更新时,我想要一个锁定组 ID 的事务,即在任何给定时间应该只有一个事务尝试更新每个 GroupId。
理想情况下应该仍然可以读取所有提交的行(即其他 transaction/ordinary 不会尝试获取“更新每组锁”的读取应该仍然能够读取)。
我想这样做的原因是更新不能依赖“过时”的数据。 IE。我确实在事务中进行了一些计算,而另一个事务无法编辑 ID 为 1 的行,也无法在第一个事务读取这些行后添加具有相同 GroupId 的新行(即使第一个事务永远不会修改行本身,它将是取决于它的价值)。
另一个“很高兴”的要求是有时我会需要相同的要求“跨组”,即更新事务必须同时锁定 2 个组。 (这不是组的动态数量,而只是 2 个)
这里有一些想法。我认为它们中的任何一个都不完美 - 我认为您需要给自己一套 use-cases 并尝试它们。我在应用锁后尝试的一些情况
- 使用 WHERE 筛选器选择另一个组
- 使用 WHERE 过滤器作为锁定组的 SELECTs
- 更新 table 将 WHERE 子句作为另一个组
- 关于 table 的更新,其中 ID(不是 GrpID!)未被锁定
- 在行被锁定的 table 更新(例如,ID 1 和 2)
- 使用那个 GrpId 插入 table
我有一种有趣的感觉,其中 none 个将是 100%,但最有可能的答案是第二个(设置事务隔离级别)。它可能会比预期锁定更多,但会为您提供所需的隔离。
还有一件事要记住:如果您锁定许多行(例如,有数千行具有您想要的 GrpId),那么 SQL 服务器可以将锁升级为 full-table 锁. (我相信临界点是 5000 锁,但不确定)。
Old-school 黑客工作
在交易开始时,以某种方式更新所有相关行,例如,
BEGIN TRAN
UPDATE YourTable
SET GrpId = GrpId
WHERE GrpId = N'G1';
-- Do other stuff
COMMIT TRAN;
没有其他东西可以使用它们,因为(太棒了!)它们是事务中的写入。
方便-设置隔离级别
在您的事务之前,将隔离级别设置得较高,例如 SERIALIZABLE。
您可能希望在事务开始时读取所有相关行(例如,SELECT Grp FROM YourTable WHERE Grp = N'Grp1'
)以锁定它们不被更新。
灵活但需要大量编码
使用 sp_getapplock and sp_releaseapplock 的资源锁定。
这些用于锁定 资源 ,而不是 table 或行。
什么是资源?好吧,任何你想要的。在这种情况下,我建议 'Grp1'、'Grp2' 等。它实际上并不锁定行。相反,您询问(通过 sp_getapplock 或 APPLOCK_TEST)您是否可以获得资源锁。如果是这样,请继续。如果没有,那就停止。
任何引用这些 table 的代码都需要审查并可能进行修改,以询问是否允许 运行。如果某事不请求许可而直接执行,则没有实际的真正锁阻止它(除非通过您明确指定的任何交易)。
您还需要确保正确处理错误(例如,仍然释放 app_lock)并且被阻止的进程是 re-tried.