MySql死锁/提交未在c#中解锁?
Posted
技术标签:
【中文标题】MySql死锁/提交未在c#中解锁?【英文标题】:MySql Deadlocks / Commit not unlocking in c#? 【发布时间】:2021-10-19 16:11:22 【问题描述】:谁能告诉我为什么下面的代码会死锁?我正在控制台应用程序中的多个线程上模拟我们的网络服务器。
控制台应用有 5 个线程,每个线程更新 250 条记录。
我发现 transaction.Commit() 是不够的,我会遇到死锁,所以它显然没有释放锁。
除非我将 transaction.Dispose() 和 Sleep(50ms) 放入,否则我总是在 innodb 上遇到死锁。如果我把代码变成一个存储过程,那么睡眠需要更大以避免死锁。我不确定它实际上是否完全避免了它们,需要使用更多线程来运行它。
在事务处理后关闭连接更可靠,但在理想情况下,在 Web 应用中,我们希望每个请求都有一个连接以提高性能。
另外,在避免死锁方面,使用 transaction.Dispose() 比使用 (var transaction = ...
我们目前使用的是 .NET,而不是 .NET 核心。
我敢打赌,如果我使用 SqlClient for Sql/Server 编写相同的程序,它会起作用 - 我明天会尝试。
谁能解释一下?我做错了什么?
static void Main(string[] args)
Console.WriteLine("GenerateBarcodesTestConsoleApp");
var connectionString = ConfigurationManager.ConnectionStrings["MyConnection"].ConnectionString;
var threads = Enumerable.Range(1, 5);
Parallel.ForEach(threads, t =>
GenerateBarcodes2(t, connectionString, 250);
);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
static void GenerateBarcodes2(int thread, string connectionString, int numberToGenerate)
using (var con = new mysqlConnection(connectionString))
con.Open();
var sql1 = "SELECT p.barcode, p..barcode_id " +
"FROM p_barcode p " +
"WHERE p.company_id = 1 " +
"AND SUBSTRING(p.barcode,1,2) = 'OK' " +
"AND players.in_use = 0 " +
"LIMIT 1 " +
"FOR UPDATE;";
var sql2 = "UPDATE p_barcode SET in_use = 1 WHERE company_id = 1 AND barcode_id = ?barcode_id AND in_use = 0";
for (int b = 0; b < numberToGenerate; b++)
using (var transaction = con.BeginTransaction(System.Data.IsolationLevel.RepeatableRead))
string barcode = string.Empty;
int barcodeId = 0;
using (var cmd = new MySqlCommand(sql1, con, transaction))
var rdr = cmd.ExecuteReader();
if (rdr.Read())
barcode = (string)rdr["barcode"];
barcodeId = (int)rdr["barcode_id"];
rdr.Close();
Console.WriteLine(barcode);
if (barcodeId != 0)
using (var cmd = new MySqlCommand(sql2, con, transaction))
cmd.Parameters.AddWithValue("barcode_id", barcodeId);
cmd.ExecuteNonQuery();
transaction.Commit();
System.Threading.Thread.Sleep(50);
//transaction.Dispose();
con.Close();
【问题讨论】:
为什么不直接执行使用有限 SELECT 的 UPDATE 语句?无需交易,一次即可预留全部 250 个条码。数据库很高兴。 Update. top n rows 单个(更新)语句使用隐式事务,但是在这个例子中这还不够,如果我取消事务处理,多个线程会得到相同的值,这根本不是想法,如果许多用户在网站上执行此操作,则每个用户(上例中的线程)都需要获取表中的唯一记录。我们需要锁定记录,以便同时运行的另一个线程不会获得相同的记录。我需要这些线程,因为它们正在模拟同时使用我们网站的许多用户。是的,我们没有那么多用户,但我正在努力确保数据库是可靠的 没有 rdms 喜欢被交易轰炸。它会锁定,但是为了测试你必须写n个线程来连接数据库,每个连接都会消耗资源 MySQL-8.0 和 MariaDB-10.6 都有SELECT ... FOR UPDATE SKIP LOCKED
,这样您的多个线程可以抓取一行,跳过现有的锁定行。
请从“show engine innodb status”的输出中添加“最新检测到的死锁”部分(它将提供更多详细信息什么锁冲突)以及“show create table p_barcode”的输出,尤其是所有索引。
【参考方案1】:
在 MariaDb 中,SKIP LOCKED 是防止死锁的解决方案。
没有防止死锁的完美解决方案,无需重新设计系统以避免两个线程同时尝试更新同一记录,但是在提交事务后添加一个小睡眠似乎有很大帮助。在我的开发机器上大约需要 20 毫秒。
这是否表明在数据库实际提交事务并释放锁之前提交返回?无论哪种方式,这种行为对于 INNODB 和 MARIADB 都是相同的。
【讨论】:
死锁告诉你有一个严重的问题。你不应该忽视它。 SKIP LOCKED 是这种情况下的明确解决方案。死锁是因为一个用户出现并锁定了记录,然后在用户将 inuse 更新为 1 之前,另一个用户出现并锁定了同一条记录。当第一个用户将 inuse 更新为 1 时发生死锁,因此没有记录可供第二个用户选择。在 INNODB 上建议第二个用户在此时重试,他们将选择下一条记录。我认为这只是设计 - 我们不能使用标识列,因为我们希望每条记录有不同的标识,这意味着很多表。以上是关于MySql死锁/提交未在c#中解锁?的主要内容,如果未能解决你的问题,请参考以下文章