添加某种形式的“PrintStatus”列。
- 添加数据时,它被设置为(比如)1,“未打印”。
- 当客户端查询要打印的数据时,仅返回 PrintStatus = 1 的数据
- 当客户端获取要打印的数据时,将其设置为(比如说)2,“正在打印”
- 当客户端完成打印后,将状态设置为 3,“已打印”
- 如果由于某种原因客户端无法打印数据,请将状态设置回 1(并将 PrintedAt 设置回 null)
这留下了看门人问题,也就是如果打印失败并且没有报告该事实会发生什么(因为看门人拔掉了服务器上的插头。别笑,它发生在这里,虽然它是一个技术维修人员,他真的应该更清楚。)如果您担心这些事情:
- 添加某种形式的时间记录,例如列“PrintedAt”,当 PrintStatus 设置为 1 时将其设置为 null,并在 PrintStatus 设置为 2 时设置为 getdate()。附带好处:现在您知道何时(或也许是“哪个打印”)您的数据被打印了!
- 打印完成后,PrintStatus 设置为 3。因此,如果 PrintStatus = 2,则打印(到目前为止)需要 getdate() – PrintedAt 运行时间。
- 定期运行定期安排的进程(SQL 代理作业对此很有用)。它检查表,如果找到 PrintStatus = 2 的任何行,其中 PrintedAt 列大于您的容差(1 天?2 小时?)。通过将 PrintStatus 设置为 1 并将 PrintedAt 设置为 null,任何此类行都会“回滚”。
这看起来有点矫枉过正,但你告诉我一致数据对你来说有多重要。
添加:
是的,并发性和一致性是个问题。虽然您可以使用BEGIN TRANSACTION ... COMMIT/ROLLBACK,但我更喜欢利用隐式事务的力量。这是一种方法:
这设置了一个包含一些数据的测试表:
-- Set up test data
CREATE TABLE Test
(
Data int not null identity(1,1)
,PrintStatus tinyint not null
,PrintedAt datetime null
)
INSERT Test (PrintStatus) values
(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
SELECT *
from Test
GO
该位包含更新所有要打印的行的 UPDATE 语句,并使用 OUTPUT 子句将这些项目存储到临时表中。然后将该表的内容返回到打印例程。
-- First pass: Get everything to pring
DECLARE @ToBePrinted table
(Data int not null)
UPDATE Test
set
PrintStatus = 2
,PrintedAt = getdate()
output inserted.Data into @ToBePrinted (Data)
where PrintStatus = 1
SELECT Data PrintThese
from @ToBePrinted
SELECT * from Test
GO
在这里,我们再添加三行数据,然后再次运行例程。旧项目未被选中,新项目被选中。
-- Second pass: Add three new items, they (and only they) get selected
INSERT Test (PrintStatus) values
(1),(1),(1)
SELECT *
from Test
DECLARE @ToBePrinted table
(Data int not null)
UPDATE Test
set
PrintStatus = 2
,PrintedAt = getdate()
output inserted.Data into @ToBePrinted (Data)
where PrintStatus = 1
SELECT Data PrintThese
from @ToBePrinted
SELECT * from Test
这是因为 SQL Server 的 ACID 属性——特别是 I)solation。一旦一条语句修改了表的内容,在该语句执行完成之前,其他语句都不能访问正在修改的数据。 (除非你傻到使用 NOLOCK。不要使用 NOLOCK。)