use*_*948 8 c# sql t-sql deadlock database-deadlocks
我有一个C#项目,它将数据写入TSQL数据库.在循环中运行有两个更新语句,例如:
for (int i = 0; i < customersProducts.Count; i++) {
CustomerProducts c = customersProducts[i];
// Update product dimensions
for (int j = 0; j < c.Count; j++) {
Product p = c[j];
updateProductDimensions(p);
}
// ... some processing
// Update product
for (int j = 0; j < c.Count; j++) {
Product p = c[j];
updateProduct(p);
}
}
Run Code Online (Sandbox Code Playgroud)
在updateProductDimensions()与updateProduct()两个触发SQL Update语句.更新的列中存在一些重叠:
string updateProductDimensions = "UPDATE products SET width = @width, height = @height, length = @length WHERE id = @id";
string updateProduct = "UPDATE products SET width = @width, height = @height, length = @length, customer_id = @customer_id, weight = @weight .... WHERE id = @id";
Run Code Online (Sandbox Code Playgroud)
示例updateProductDimensions()方法 - updateProduct()也类似:
public void updateProductDimensions(Product p) {
SqlConnection connection = DBFactory.getConnection();
string updateProductDimensions = "UPDATE products SET width = @width, height = @height, length = @length WHERE id = @id";
try
{
SqlCommand sqlCmd = new sqlCmd(updateProductDimensions, connection);
sqlCmd.Parameters.AddWithValue("@width", 20);
sqlCmd.Parameters.AddWithValue("@height", 10);
sqlCmd.Parameters.AddWithValue("@length", 30);
sqlCmd.Parameters.AddWithValue("@id", p.id);
sqlCmd.CommandType = CommandType.Text;
sqlCmd.ExecuteNonQuery();
}
catch (Exception e)
{
// Handle exception
}
finally
{
connection.Close();
}
}
Run Code Online (Sandbox Code Playgroud)
我运行了一个SQL Server死锁跟踪,它显示该updateProduct语句失败(即受害者进程),幸存的进程是运行该updateProductDimensions语句的进程.
死锁跟踪的简化版本如下(最新的进程优先):
- updateProduct2: fail
- updateProduct2: success
- updateProduct1: success
- updateProductDimensions4: success
- updateProductDimensions3: success
- updateProductDimensions2: success
- updateProductDimensions1: success
Run Code Online (Sandbox Code Playgroud)
每行代表每次for loop迭代更新一个产品.
以及资源/所有者列表updateProduct2:
- owner: updateProductDimensions1 (mode = U, isolationLevel = read committed (2))
- waiter: updateProduct2 (mode= U, requestType = wait, isolationLevel = read committed (2))
Run Code Online (Sandbox Code Playgroud)
我的问题是,为什么会出现死锁?即使两个语句更新同一行,它也是同一个表.服务器与多个客户端通信,客户端只能更新自己的产品 - 即.单个产品只能由一个特定客户更新.这样,多个数据库更新同时发生,但对于不同的行(产品).
如何在不删除重复的更新列的情况下解决这个问题?
为products表创建语句:
CREATE TABLE Products (
[id] VARCHAR (255) NOT NULL,
[width] INT NOT NULL,
[length] INT NOT NULL,
[height] INT NOT NULL,
[weight] INT NOT NULL,
// more fields
[customer_id] INT CONSTRAINT [F_KEY_CUSTOMER] DEFAULT ((0)) NOT NULL,
CONSTRAINT [P_KEY_PRODUCT] PRIMARY KEY CLUSTERED ([id] ASC),
CONSTRAINT [F_KEY_CUSTOMER] FOREIGN KEY ([customer_id]) REFERENCES [dbo].[Customer] ([id])
);
Run Code Online (Sandbox Code Playgroud)
查询计划
更新产品维度声明:
更新产品声明:
死锁痕迹
<TextData>
<deadlock-list>
<deadlock victim="victimProcess">
<process-list>
<process id="victimProcess" taskpriority="0" logused="0" waitresource="PAGE: 15:1:1259" waittime="4594" ownerId="21610772296" transactionname="UPDATE" lasttranstarted="2018-02-21T08:46:44.777" XDES="0x859b9c580" lockMode="U" schedulerid="20" kpid="34240" status="suspended" spid="64" sbid="3" ecid="3" priority="0" trancount="0" lastbatchstarted="2018-02-21T08:46:44.777" lastbatchcompleted="2018-02-21T08:46:44.777" clientapp=".Net SqlClient Data Provider" hostname="" hostpid="636" isolationlevel="read committed (2)" xactid="21610772296" currentdb="15" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="adhoc" line="1" stmtstart="422" sqlhandle="0x02000000696bc4026d3a5eb5fc3835e32324ce9f3e4bdd28">
UPDATE products SET width = @width, height = @height, length = @length, customer_id = @customer_id, weight = @weight WHERE id = @id </frame>
<frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
</inputbuf>
</process>
<process id="survivorProcess4" taskpriority="0" logused="0" waitresource="PAGE: 15:1:2795" waittime="4593" ownerId="21610772296" transactionname="UPDATE" lasttranstarted="2018-02-21T08:46:44.777" XDES="0x45ebe3ca0" lockMode="U" schedulerid="18" kpid="254204" status="suspended" spid="64" sbid="3" ecid="6" priority="0" trancount="0" lastbatchstarted="2018-02-21T08:46:44.777" lastbatchcompleted="2018-02-21T08:46:44.777" clientapp=".Net SqlClient Data Provider" hostname="" hostpid="636" isolationlevel="read committed (2)" xactid="21610772296" currentdb="15" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="adhoc" line="1" stmtstart="422" sqlhandle="0x02000000696bc4026d3a5eb5fc3835e32324ce9f3e4bdd28">
UPDATE products SET width = @width, height = @height, length = @length, customer_id = @customer_id, weight = @weight WHERE id = @id </frame>
<frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
</inputbuf>
</process>
<process id="survivorProcess3" taskpriority="0" logused="224" waitresource="PAGE: 15:1:2795" waittime="4527" ownerId="21610772095" transactionname="UPDATE" lasttranstarted="2018-02-21T08:46:44.680" XDES="0x859b9c300" lockMode="U" schedulerid="20" kpid="16324" status="suspended" spid="123" sbid="2" ecid="1" priority="0" trancount="0" lastbatchstarted="2018-02-21T08:46:44.680" lastbatchcompleted="2018-02-21T08:46:44.673" clientapp=".Net SqlClient Data Provider" hostname="" hostpid="636" isolationlevel="read committed (2)" xactid="21610772095" currentdb="15" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="adhoc" line="1" stmtstart="102" sqlhandle="0x020000007e9c95155af7dd6044d8697705c48a1d5856dba4">
UPDATE products SET width = @width, height = @height, length = @length WHERE id = @id </frame>
<frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
</inputbuf>
</process>
<process id="survivorProcess2" taskpriority="0" logused="224" waitresource="PAGE: 15:1:1259" waittime="4529" ownerId="21610772095" transactionname="UPDATE" lasttranstarted="2018-02-21T08:46:44.680" XDES="0x270bf8b20" lockMode="U" schedulerid="13" kpid="406864" status="suspended" spid="123" sbid="2" ecid="4" priority="0" trancount="0" lastbatchstarted="2018-02-21T08:46:44.680" lastbatchcompleted="2018-02-21T08:46:44.673" clientapp=".Net SqlClient Data Provider" hostname="" hostpid="636" isolationlevel="read committed (2)" xactid="21610772095" currentdb="15" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="adhoc" line="1" stmtstart="102" sqlhandle="0x020000007e9c95155af7dd6044d8697705c48a1d5856dba4">
UPDATE products SET width = @width, height = @height, length = @length WHERE id = @id </frame>
<frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
</inputbuf>
</process>
<process id="survivorProcess1" taskpriority="0" logused="10000" waittime="4315" schedulerid="17" kpid="30464" status="suspended" spid="123" sbid="2" ecid="0" priority="0" trancount="2" lastbatchstarted="2018-02-21T08:46:44.680" lastbatchcompleted="2018-02-21T08:46:44.673" clientapp=".Net SqlClient Data Provider" hostname="" hostpid="636" loginname="" isolationlevel="read committed (2)" xactid="21610772095" currentdb="15" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="adhoc" line="1" stmtstart="102" sqlhandle="0x020000007e9c95155af7dd6044d8697705c48a1d5856dba4">
UPDATE products SET width = @width, height = @height, length = @length WHERE id = @id </frame>
<frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
(@width int,@height int,@length int,@id nvarchar(255))UPDATE products SET width = @width, height = @height, length = @length WHERE id = @id </inputbuf>
</process>
</process-list>
<resource-list>
<pagelock fileid="1" pageid="1259" dbid="15" objectname="MyDB.dbo.Product" id="lock15a855b00" mode="U" associatedObjectId="72057594038845440">
<owner-list>
<owner id="survivorProcess1" mode="U" />
</owner-list>
<waiter-list>
<waiter id="victimProcess" mode="U" requestType="wait" />
</waiter-list>
</pagelock>
<pagelock fileid="1" pageid="2795" dbid="15" objectname="MyDB.dbo.Product" id="lockbb9f0f80" mode="U" associatedObjectId="72057594038845440">
<owner-list>
<owner id="survivorProcess1" mode="U" />
</owner-list>
<waiter-list>
<waiter id="survivorProcess4" mode="U" requestType="wait" />
</waiter-list>
</pagelock>
<pagelock fileid="1" pageid="2795" dbid="15" objectname="MyDB.dbo.Product" id="lockbb9f0f80" mode="U" associatedObjectId="72057594038845440">
<owner-list />
<waiter-list>
<waiter id="survivorProcess3" mode="U" requestType="wait" />
</waiter-list>
</pagelock>
<pagelock fileid="1" pageid="1259" dbid="15" objectname="MyDB.dbo.Product" id="lock15a855b00" mode="U" associatedObjectId="72057594038845440">
<owner-list />
<waiter-list>
<waiter id="survivorProcess2" mode="U" requestType="wait" />
</waiter-list>
</pagelock>
<exchangeEvent id="Pipe49e4ca380" WaitType="e_waitPipeGetRow" nodeId="2">
<owner-list>
<owner id="survivorProcess3" />
<owner id="survivorProcess2" />
</owner-list>
<waiter-list>
<waiter id="survivorProcess1" />
</waiter-list>
</exchangeEvent>
</resource-list>
</deadlock>
</deadlock-list>
</TextData>
Run Code Online (Sandbox Code Playgroud)
这个问题没有包含足够的场景让我能够复制这个例子,所以我要推测。
SqlCommand 是一次性的;但不在 using 块中,并且没有被释放,所以我怀疑当后续命令发生时,前一个命令仍在干扰数据库。
将两个 SqlCommand 放入“using”块中;当你这样做时,删除“finally{connection.Close();}”,并将 SqlConnection 放入“using”块中(Dispose 将执行 Close)。