我正在使用一个应用程序,它根据符合特定条件的最后一行向数据库添加一个新行。是否有处理此类问题的标准模式,或者我只是需要锁定表格?
这是一个过度简化的可视化:
A1
A2
A3
B1
B2
使用上面的可视化,网页加载最高的“B”值,即“2”。然后,经过一段时间后,它想要插入 B3,即系列中的下一条记录。但是,它必须检查以确保其他人不做同样的事情。
就像我提到的,我知道我可以读取事务中的预期值,或者我可以锁定表,甚至可能锁定最后一行。我问的是,是否有推荐的策略。
答案 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_id
和prev_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样本,但我认为这是你的问题的意义。
答案 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列,它会飞。