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#中解锁?的主要内容,如果未能解决你的问题,请参考以下文章

使用 C# 在 Mysql 上死锁 - “超过锁定等待超时;尝试重新启动事务”

MySQL死锁的详细分析方法

请教一个MYSQL中死锁的问题

sqlServer查看死锁及解锁

db2 如何查看死锁的详细信息

mysql操作中卡死 解决方法