使用数据库跟踪对象的锁定/解锁


8

我需要跟踪对象的锁定/解锁动作。在对对象(合同,合作伙伴等)执行任何操作之前,将lock发出一个事件。操作完成后,它将发出unlock事件。

我想获取那些已锁定但尚未解锁的对象。目的是使查询快速并避免死锁。

下表是

create table locks (
    id int identity,
    name varchar(255),
    lock int
) 

insert into locks values('a', 1)
insert into locks values('b', 1)

insert into locks values('c', 1)
insert into locks values('d', 1)

insert into locks values('a', 0)
insert into locks values('c', 0)


insert into locks values('a', 1)
insert into locks values('b', 1)

我使用下面的查询来对象尚未解锁的对象:

select distinct m.name from locks m
    where (select COUNT(id) from locks locked
           where locked.lock = 1 and locked.name = m.name)
        >  (select COUNT(id) from locks unlocked
            where unlocked.lock = 0 and unlocked.name = m.name)

它正确且结果有效ab并且d

我的问题是:-我的解决方案是否足以避免死锁?如果INSERT在查询执行期间有很多问题,可能会发生任何问题吗?-您还有其他(更好)的方法来解决此问题吗?

更新

对于没有将上下文放入问题,我深表歉意。上面的数据库设计不能替代数据库锁定。

我们有一个外部系统,我们从系统中称它为。它要求在对对象(可以是合同或合作伙伴)执行的每个操作之前,在其系统上调用lockunlock方法。

最近,我们遇到了这样的情况,服务器崩溃了,我们必须重新启动它。不幸的是,已经调用的正在运行的进程lock没有机会调用unlock以释放对象,因此当我们的系统再次连接到外部程序时,导致了其他一些问题。

因此,我们希望提供一种跟踪每个lock呼叫的功能。重新启动服务器后,我们将调用unlock先前锁定的对象。

感谢Remus Rusanu指出我的问题是使用原型 DDL。这是我第一次在DBA上发布问题,对于没有阅读FAQ表示歉意。

谢谢

Answers:


11

目的是使查询快速并避免死锁。

这是一个不现实的目标。死锁由获取锁的应用程序确定,并且无法控制实现锁的方式。您最好的期望是检测死锁。

将锁实现为记录是有问题的。行持续存在,即使实现是完美的,您也会在应用程序崩溃时泄漏锁。用applock锁定。它们具有清晰的事务语义,并且死锁被引擎检测到。

然而,看着你想达到什么样的,是不可能的,你需要的锁。您正在描述一个队列(选择下一个可用于处理的项目,并避免冲突=>队列,而不是锁定)。阅读“ 将表用作队列”

至于您的特定实现(使用保留锁定历史记录的表),我必须诚实:这是一场灾难。从表设计开始完全不能满足预期的用途:按名称查询,但是表是没有索引的堆。没有明显的原因,它具有一个标识列。您可能会回答它只是一个“伪代码”表,但这是DBA.SE,您不要在此处发布不完整的DDL!

但更重要的是,该实现未实现锁定!没有阻止两个用户从“锁定”同样的对象两次。您的“锁定”完全取决于呼叫者魔术正确地表现。甚至最好的书面应用程序也无法使用此锁定,因为无法自动检查和获取该锁定。两个用户可以检查,确定“ a”已解锁,并同时插入('a', 1)记录。在非常至少,你需要的唯一约束。当然哪个会破坏“计数锁与解锁以确定状态”的语义。

不好意思,但这是F级的实现。

更新

因此,我们希望提供一种跟踪每个锁调用的功能。重新启动服务器后,我们将对先前锁定的对象调用解锁。

无需与远程系统进行两阶段提交分布式事务,这就是“尽力而为”,因为在第三方系统上编写“解锁”和实际调用“解锁”之间存在许多竞争条件。 。尽力而为,这是我的建议:

  • 创建一个简单的表来跟踪锁:

    CREATE TABLE locks (name VARCHAR(255) NOT NULL PRIMARY KEY);

  • 在调用之前,lock将锁插入到表中并提交

  • 调用后,从表中unlock 删除并提交
  • 在系统启动时,请查看下表。任何行都没有上一次运行的剩余锁,必须“解锁”。呼叫unlock每一行,然后删除该行。只有在所有未决锁定都已“解锁”之后,您才能恢复应用程序的正常功能。

我提议这样做是因为桌子会很小。在任何时候它仅包含当前的活动锁。它不会增加广告的恶心,并且由于大小过大而导致以后出现问题。看到锁定的内容很简单。

当然,此实现不提供由谁和何时锁定哪些内容的审核历史记录。您可以根据需要在另一个表中添加该表,仅在其中插入事件(锁定或解锁),而从不查询以找出“孤立”锁。

您仍然必须准备好调用“解锁”才能在启动期间失败,因为您不能保证调用unlock删除行之前系统不会崩溃(换句话说,表和第三方系统已经分开并且具有不同的特性)版本的真相)。同样,你不能阻止这种W / O分布式事务和我永远不会叫 DTC。


4

这将杀死应用程序上的并发性。SQL Server已经拥有避免同时更新同一行所需的一切,因此实际上没有必要这样做。

死锁的发生可能有多种原因,因此建议您在构建自己的锁定系统之前先调查这些原因。您可以从系统运行状况XE会话中捕获死锁图,然后开始对其进行分析。

通常,死锁是对对象以不同顺序进行锁定的结果,因此您应尝试始终以相同顺序进行锁定。造成死锁的原因还有其他原因,但是一般的指导原则是,短事务会在短时间内保持锁定状态,因此,使用更好的代码,索引和Divet-et-impera策略来优化查询可能是摆脱困境的最佳方法。僵局。

通过将数据库切换为“ 读取提交的快照隔离 ”,可以大大缓解“读者阻止作家”的僵局。如果在构建应用程序时考虑到悲观锁定,则应在数据库上激活此选项之前仔细检查和测试所有内容。

如果您坚持使用“自定义锁定系统”路线,请谨慎使用可保证在应用程序出现问题时释放锁的工具。您可能想研究内置的sp_getapplock存储过程,它所做的事情与您似乎想做的类似。

更新:阅读完已编辑的问题后,这是表达相同查询的另一种方法:

SELECT *
FROM (
    SELECT *, RN = ROW_NUMBER() OVER(PARTITION BY name ORDER BY id DESC) 
    FROM locks
) AS data
WHERE RN = 1 
    AND lock = 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.