我遇到一个问题,即使用完全相同的参数在同一时间调用相同的存储过程。
存储过程的目的是获取记录(如果存在),或者创建并获取记录(如果它不存在)。
问题是两个线程都在检查记录是否存在并报告为false,然后两者都插入一条新记录,在数据库中创建一个副本。
我尝试将操作粘贴在事务中,但这只会产生数百个死锁。
有没有什么方法可以用线程安全的方式检查记录是否存在,这样第二个线程在第一个完成插入之前就不会读取它?我无法控制线程本身,只能控制它们正在执行的存储过程。
任何帮助将不胜感激,
感谢。
答案 0 :(得分:8)
诀窍是在INSERT语句中添加一个WHERE,这样INSERT只有在项不存在时才有效,后跟SELECT语句。假设您可以通过ID列标识记录:
INSERT INTO MyTable (ID,Col1,Col2,...)
SELECT @IDValue,@Col1Value,@Col2Value, ...
WHERE NOT EXISTS (SELECT ID
FROM MyTable
WHERE ID=@IDValue)
SELECT *
FROM MyTable
Where ID=@IDValue
您不需要将语句放在事务中,因为每个语句都在其自己的隐式事务中执行。因此,两个INSERTS无法同时成功。
编辑:INSERT ... SELECT语法是必需的,因为TSQL不允许INSERT语句中的VALUES和WHERE部分。
答案 1 :(得分:0)
麻烦在于做一个选择然后插入然后通常有一个读锁定用于选择,然后插入一个写锁定。如果没有事务处理,那么许多更新的时间通常会允许多次插入发生,如您所见。在一个事务中,第一个读锁定将阻止其他进程获得写锁定,如果多个进程获得读锁定,则没有一个进程可以获得写锁定,从而导致死锁。
在这种情况下,我会更改插入代码,以便索引只允许一个插入工作,即您有一个唯一的键,只有一个进程将能够插入数据,因此不会重复。 然后,更新过程是在事务中
1)首先执行插入操作,如果尝试插入重复的
,则处理异常或错误或2)在首先执行选择时执行HOLD LOCK(Sybase和SQL Server) - 因此第一个锁定获取在需要时插入的完全权限
或3)如果RDBMS允许,可能使用merge命令。这会检查并在一个命令中插入所有命令但总是会更改数据库。
编辑: 我认为如果你需要确保只插入一条记录,那么就没有真正的替代方法,因为测试必须在交易中。
可以通过检查一个事务中的存在forst然后再执行插入并检入另一个事务来削减成本。因此,在大多数情况下,您只需要一个选择,而在其他情况下,您可以获得完整的慢速插入和检查,但这种情况应该不常发生。
答案 2 :(得分:0)
不确定SQL Server是否拥有它。但是在MySQL和oracle中,你可以在使用 for update 语法进行选择时获得写锁定。
select *
from table
for update
由于其他线程在执行select时也需要写锁定,因此它们将等到第一个线程完成事务。
答案 3 :(得分:0)
我假设您正在使用c#与sql server进行通信,然后您可以尝试查看任务并行性和任务库,以便对存储过程进行多线程处理。