【发布时间】: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”的输出,尤其是所有索引。