我正在编写一个简单的消息传递程序,其中有一个消息表,可以由用户声明并由该用户完成。并不是预定了哪个用户会声明给定的消息,所以我想要一个查询来选择我拥有的所有可用消息中的第一个,然后用一个来标记该消息,我也有。问题是,我不希望两个用户同时使用它来声明相同的消息,因此我想连续运行这两个语句,而不必返回程序以找出下一个要运行的内容之间的陈述。我相信我可以通过用分号分隔它们来运行两个连续的语句,但我想使用第一个查询中返回的数据作为第二个查询的一部分。变量将是完美的,但据我所知,它们在SQL中不存在。有什么办法可以在查询之间保留状态吗?
答案 0 :(得分:2)
这是BEGIN TRAN和COMMIT TRAN的用途。在交易中放置您要保护的报表。
答案 1 :(得分:1)
有什么方法可以在查询之间保留状态吗?
没有。 SQL不是一种过程语言。您可以将两个查询重写为单个查询(并非总是可行,即使可能也不值得),或者使用过程语言将它们粘合在一起。许多SQL服务器为此提供了内置语言(“存储过程”),或者您可以在应用程序中执行此操作。
问题是,我不希望两个用户同时使用它来声明相同的消息
使用锁。我不知道你正在使用什么SQL服务器,但使用SELECT ... FOR UPDATE
听起来就像你想要的那样,如果可用的话。
答案 2 :(得分:0)
交易是一种很好的方式,就像le dorfier所说的那样,但是有一些不适用的东西:
您可以先进行更新,即使用用户ID或类似标记邮件。你不提到你使用的是哪种sql风格,但在mysql中,我认为它看起来像这样:
UPDATE message
SET user_id = ...
WHERE user_id = 0 -- Ensures no two users gets the same message
LIMIT 1
在ms sql中,它有点像:
WITH q AS (
SELECT TOP 1
FROM message m
WHERE user_id = 0
)
UPDATE q
SET user_id = 1
/ B
答案 3 :(得分:0)
您也许可以使用临时表。
答案 4 :(得分:0)
SQL本身没有变量,但(几乎?)所有RDBMS SQL扩展都有。但是,我不确定如何解决你的问题。
如上所述,交易可以解决问题 - 有效地将2个不相关的语句组合在一起。 但是,默认的事务级别无法正常工作。 (大多数?)RDBMS服务器的默认事务级别是READ COMMITTED。这不会阻止用户2读取用户1读取的同一行。为此,您需要使用REPEATABLE READ或SERIALIZABLE。
这是一个经典的并发问题。通常,处理它的两种方式是悲观锁定或乐观检查。一个REPEATABLE READ事务将是悲观的(无论是否需要锁定费用),并且检查@@ ROWCOUNT是乐观的(假设它可行,但在@@ ROWCOUNT = 0时做一些合理的事情)。
通常,我们使用乐观(锁定很昂贵),并使用时间戳或读取的字段组合来确保我们正在改变我们想到的数据。所以,我的建议是包含一个rowversion或timestamp字段,并将其传递回UPDATE语句。然后,检查@@ ROWCOUNT以查看是否更新了任何记录。如果没有,请返回并选择另一条消息。在伪代码中:
int messageId, byte[] rowVersion = DB.Select(
"SELECT TOP 1
MessageId, RowVersion
FROM Messages
WHERE
User IS NULL";
int rowsAffected = DB.Update(
"UPDATE Messages SET
User = @myUserId
WHERE
MessageId = @messageId
AND RowVersion = @rowVersion",
myUserId, messageId, rowVersion
);
if (rowsAffected = 0)
throw new ConcurrencyException("The message was taken by someone else");
根据您的特定语句,您可以在UPDATE语句中重复“UserId IS NULL”WHERE子句。这与Brimstedt的解决方案类似 - 但您仍然必须检查@@ ROWCOUNT 以查看行是否实际更新。