添加行时需要数据库并发 - 最佳实践?

时间:2009-06-15 17:36:28

标签: sql sql-server concurrency

我正在使用一个应用程序,它根据符合特定条件的最后一行向数据库添加一个新行。是否有处理此类问题的标准模式,或者我只是需要锁定表格?

这是一个过度简化的可视化:

A1
A2
A3
B1
B2

使用上面的可视化,网页加载最高的“B”值,即“2”。然后,经过一段时间后,它想要插入 B3,即系列中的下一条记录。但是,它必须检查以确保其他人不做同样的事情。

就像我提到的,我知道我可以读取事务中的预期值,或者我可以锁定表,甚至可能锁定最后一行。我问的是,是否有推荐的策略。

10 个答案:

答案 0 :(得分:1)

在我的博客中查看此条目,了解如何使用递归CTE和单IDENTITY来执行此操作:

<强>更新

如果问题是将设备构建到下一步,那么您最好使用绝对值而不是相对值。

记住变量中步骤的先前值(在页面本身或服务器端),然后使用变量的新值更新它。

而不是:

UPDATE  mytable
SET     step = step + 1

使用它:

SET @nextstep = 2
UPDATE  mytable
SET     step = @nextstep

您还可以在列中添加自动增量last_update字段,以确保在页面加载后更新未更新的列:

SELECT  last_update
INTO    @lastupdate
FROM    mytable
WHERE   item_id = @id

UPDATE  mytable
SET     step = @nextstep
WHERE   item_id = @id
        AND last_update = @lastupdate

更新2

如果您使用状态链接列表(即您没有更新,但插入新状态),则只需标记列IDENTITY并插入先前状态的ID:< / p>

item_id  step_id  prev_step_id
1        10232     0
1        12123     10232

,将step_idprev_step_id设为唯一,并按此查询:

WITH    q (item_id, step_id, step_no) AS
        (
        SELECT  item_id, step_id, 1
        FROM    mytable
        WHERE   item_id = 1
                AND prev_step_id = 0
        UNION ALL
        SELECT  q.item_id, m.step_id, q.step_no + 1
        FROM    q
        JOIN    mytable m
        ON      m.item_id = q.item_id
                m.prev_step_id = q.step_id
        )
SELECT  *
FROM    q

如果两个人想要插入两条记录,那么UNIQUE上的prev_step_id约束将会触发,最后一次插入将失败。

答案 1 :(得分:1)

这对我来说就像需要操作员的经典案例一样:“确保满足这些条件的元组存在,并给我钥匙。”

在我的情况下,通常很简单,“我有这个信用卡号和有效期,它的关键是什么?”我实际上并不关心它是否已经在数据库中(事实上,为了安全起见,应用程序不应该告诉我)我只想要它的标识符,或者我希望它是如果不是,则创建,并获取该创建的新标识符。

据我所知,使用当前的DBMS技术,您需要锁定表,因为您必须根据已经存在的内容决定是否插入。不过,我希望有更好的解决方案。

答案 2 :(得分:1)

从概念上讲,如果我理解澄清的积累,那么情况就是你要记录一个物品已经进入一个新的状态 - 一件装备已达到某个步骤。并且您希望在增加目前被认为存在的步骤的基础上执行此操作。

我会重申这一点可能更易于管理和明确。您是否可以简单地插入一条记录,声明机器在状态下被观察到,并带有时间戳?

从先前信息(可能本身不完全已知)中获取当前步骤似乎存在风险,特别是如果它是一个简单的迭代计算,可以根据情况从0到n次发生。

OTOH,如果它是对实际状态进行时间戳记的观察,那么它就是自我纠正(你认为它之前处于什么状态并不重要),并且多个断言不会引起问题。

你能否根据现有表格(或者对表格的简单修改或网络限制或其他方式)以这种方式重建逻辑?是否存在与步骤子集的给定步骤相关联的用户或IP地址等?是否存在仅在步骤或步骤子集中有效的关联事务?

答案 3 :(得分:0)

这是使用队列的完美案例。试试Message Broker。

答案 4 :(得分:0)

我知道您的示例已经过简化,但为了满足这些要求,您可以执行以下操作。

由于INSERT / SELECT是一个语句,它隐含在一个事务中。如果你需要做一些你无法表达关系的事情,你需要将它包装在一个明确的交易中。

主键可确保在给定的默认事务隔离之外不会出现并发问题。

CREATE TABLE Sequence
(
    [Name] char(1),
    [Seq] int
    PRIMARY KEY (Name, Seq)
)
GO
CREATE PROCEDURE Sequence_Insert
(
    @name char(1)
)
AS
INSERT INTO Sequence(Name, Seq)
SELECT 
    @Name,
    COALESCE(MAX(Seq),0) + 1
FROM
    Sequence
WHERE
    Name = @Name
GO
exec Sequence_Insert 'A'
exec Sequence_Insert 'A'
exec Sequence_Insert 'B'
exec Sequence_Insert 'A'
exec Sequence_Insert 'C'
GO
SELECT * FROM Sequence

答案 5 :(得分:0)

将其包装在同一个语句中。第一次插入新值(2)将成功,第二次将添加零行。

create table t1 (i int)
insert t1 values (1)

insert t1 (i)
    select 2 where exists (select max(i) from t1 having max(i) = 1)

insert t1 (i)
    select 2 where exists (select max(i) from t1 having max(i) = 1)

答案 6 :(得分:0)

正确的策略取决于读取B2和插入B3之间发生的操作究竟是什么。以下几点是理论上的,而不是实用的T-SQL样本,​​但我认为这是你的问题的意义。

  • 如果这是一个带有一次性结果的便宜动作,那么你可以让每个事务都这样做,然后插入B3的第一个成功,其余的重复密钥违规失败(唯一约束),从异常中恢复并恢复,就像什么也没发生。
  • 相对昂贵的手术,具有一次性结果但不太可能同时发生。与上述相同,您将采取处罚“昂贵”操作结果的处罚,但这种情况很少发生。
  • 可以回滚的非一次性结果(可以嵌入到您的交易中,或者您可以注册到DTC),并且不太可能同时发生。与上述相同,但在您回滚的冲突中将“操作”注册到您的交易中(本地或DTC),然后再次使用Bs序列的新值重试。
  • 结果是非一次性的('操作'的结果不能忽略,必须记录,例如,您记录了金融交易)。在这种情况下,您必须防止并发和锁定是要走的路。表锁总是矫枉过正,你可能应该在'B2'之后的查找中使用UPDLOCK。不幸的是,事务隔离级别在这里没有任何帮助(在所有级别上,两个线程都可以读取'B2'并向前跳跃,因此,希望在插入时出现死锁)。如果读取B2和插入B3之间的“操作”就像将HTML返回给用户并等待下一个POST一样复杂,那么你可能无法负担将这些长期存在的U锁留在真实数据上并且最好方法是使用sp_getapplock设置应用程序锁架构。

答案 7 :(得分:0)

推荐的SQL策略肯定是使用SELECT FOR UPDATE。我很惊讶没有人提到它。

SELECT id FROM tasks WHERE id = max(id) FOR UPDATE OF tasks;

SELECT FOR UPDATE完全锁定你需要锁定的东西,所以它比手动锁定简单得多。

答案 8 :(得分:-1)

UPDATE yourtable
  SET  location = 'B3'
 WHERE primary-key = 1231421
   AND location    = 'B2'

如果某人已将其移出B2,则不会发生任何事情。这似乎比简单地盲目增加位置更好;用户希望它从B2转到B3,而不是向前推进。

好的,考虑到新的行要求:

INSERT INTO yourtable ( item, location ) VALUES( 123, 'B3' )
 WHERE NOT EXISTS( SELECT * FROM yourtable WHERE item = 123 AND location = B3 )

让数据库为您完成工作。

答案 9 :(得分:-1)

执行此操作的常用方法是使用rowversion类型列并使用从客户端传入的值检查行值。 如果表中的行已更新,则rownumbers将不匹配。 索引rowversion列,它会飞。