【问题标题】:Insert Concurrency Issue - Multithreaded Environment插入并发问题 - 多线程环境
【发布时间】:2011-03-07 15:28:45
【问题描述】:

我有一个问题,即在完全相同的时间使用完全相同的参数调用相同的存储过程。

存储过程的目的是在记录存在时获取记录,如果记录不存在则创建并获取记录。

问题是两个线程都在检查记录的存在并报告错误,然后两个线程都在插入一条新记录,在数据库中创建了一个副本。

我尝试将操作保留在事务中,但这只会产生数百个死锁。

有什么方法可以以线程安全的方式检查记录是否存在,以便第二个线程在第一个线程完成插入之前不会进行读取?我无法控制线程本身,只能控制它们正在执行的存储过程。

任何帮助将不胜感激,

谢谢。

【问题讨论】:

  • 它产生了死锁,这些事务到底在做什么;它们不能像您所说的那样简单。您使用的是什么 RDBMS?
  • 相反,过程非常简单——检查记录是否已经存在,如果不存在则创建它,然后选择记录。我们遇到问题的唯一原因是并发线程很多(最多 200 个)。
  • 哦,我忘了说 - 使用 SQL Server 2008。

标签: sql-server multithreading sql-server-2008 concurrency


【解决方案1】:

问题在于进行选择然后插入,然后通常对选择有一个读锁定,然后对插入有一个写锁定。如果没有事务,那么许多更新的时间通常会允许多次插入发生,如您所见。在事务中,第一个读锁将阻止其他进程获得写锁,如果多个进程获得读锁,则没有一个进程可以获得写锁,因此您会遇到死锁。

在这种情况下,我将更改插入代码,以便索引只允许一个插入工作,即您有一个唯一的键,并且只有一个进程能够插入数据,因此不会出现重复。 然后更新的过程在一个事务中

1) 先进行插入,如果尝试插入重复项,则处理异常或错误

或 2) 在先执行选择时执行 HOLD LOCK(Sybase 和 SQL Server) - 因此第一个锁定的人将获得插入的完整权限(如果需要)

或 3) 如果 RDBMS 允许,可能使用合并命令。这会在一个命令中完成检查并插入所有内容,但总是会更改数据库。

编辑: 如果您需要确保插入一个且只有一个记录作为测试必须在事务中插入,我认为没有真正的替代方法 1。

可以通过在一个事务中检查 forst 的存在,然后在另一个事务中进行插入和检查来降低成本。因此,在大多数情况下,您只有一个选择,而在其他情况下,您会获得完整的慢速插入和检查,但这种情况应该很少发生。

【讨论】:

  • 我已经尝试过第一个解决方案,但它减慢了进程太多 - 也尝试了保持锁,但这并没有解决问题,因为两个线程已经在表上拥有共享锁并且两者都不能提升。不确定合并命令,但如果它总是修改数据库,那么这不是我想要的。
【解决方案2】:

不确定 SQL Server 是否有它。但是在 MySQL 和 oracle 中,您可以在使用 for update 语法进行选择时获得写锁。

select * 
from table 
for update

由于其他线程在做select时也需要写锁,所以它们会等到第一个线程完成事务。

【讨论】:

  • 这与事务具有相同的效果 - 它在表上创建锁。这种没有 where 语句的特殊语法实际上会锁定整个表。 SQL Server 没有这种语法,但即使使用 Oracle,与更简单的版本相比,这种语法也会导致延迟。
【解决方案3】:

诀窍是在 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 部分。

【讨论】:

  • 我想知道您是否应该在内部选择语句中添加 WITH (UPDLOCK, HOLDLOCK)?
  • 为了什么?操作是原子的。 HOLDLOCK 只会以与事务完全相同的方式保持锁定,直到连接关闭或包含事务(如果有)完成。事实上,HOLDLOCK 比显式事务更糟糕,因为在连接关闭之前你无法控制锁
【解决方案4】:

我假设您正在使用 c# 与 sql server 进行通信,那么您可以尝试查看任务并行性和任务库以对存储过程进行多线程处理。

【讨论】:

  • 更改访问外部资源的技术不太可能解决并发问题。如果是,它也可能无效,因为我们不确定客户端是什么。
猜你喜欢
  • 2020-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-03
  • 2014-01-20
  • 2014-11-02
  • 2015-11-11
  • 1970-01-01
相关资源
最近更新 更多