randomcic 2023-03-12 07:49 采纳率: 100%
浏览 16
已结题

mysql回滚操作有时会出现已经写入一条命令丢失另一条命令。

请教各位一个问题:使用asp.net 做的一个报名系统,因为课程很抢手,大概会在2分钟左右就抢完了。
系统有2张表,表1存有相关课程内容、招生人数(大约20条),表2存有人员名单信息(大概2000人)。使用mysql数据库事务来同时写入2张表,我的思路是:如果表2相应的课程已报名人数<招生人数时,则表2已报名人数+1,同时表1中报名的同学记录课程代码;否则则回滚操作。

主要SQL命令如下 (请各位原谅我这个业余使用中文表字段及变量)

SQL(0) = "update1 set 代码='" & 课程代码 & "' , 日期='" & 报名时间 & "' where id='" & 姓名ID & "' LIMIT 1"
SQL(1) = "update2 set 已报人数=已报人数+1 where 已报人数<招生人数 and 代码='" & 课程代码 & "'"

实际使用中会出现表2某一课程已报人数>表1中实际统计的该课程的数量。这种情况不一定出现在哪一课程上,系统在实际中已经运行了3次,每次都是这种结果。 也就是说有时候,回滚操作会出现SQL(1)命令已经写入,但是SQL(0)命令没有写入的情况。 可是正常情况下,事务和回滚不是都成组处理嘛,怎么还会有这种情况呢。

请教各位出现这种情况是因为我的思路的问题,还是程序设计或是数据库表的问题? 最好有类似问题的解决代码,请各位指点一二。由于本人只是个业余选手,问题描述时可能词语不太准确。

  • 写回答

3条回答 默认 最新

  • MarkHan_ 2023-03-12 08:56
    关注

    该回答引用GPTᴼᴾᴱᴺᴬᴵ
    根据您的描述,可能是由于多个客户端同时访问数据库导致的竞态条件(race condition)造成的问题。具体来说,可能是在两个客户端同时尝试更新表2中的某个课程的已报人数时,由于MySQL使用的是MVCC(多版本并发控制)机制,导致其中一个客户端看到的是旧版本的已报人数,另一个客户端看到的是新版本的已报人数,最终导致已报人数的增加数量超过了招生人数限制。
    ·
    为了避免这种情况的发生,您可以考虑使用行级锁或者事务隔离级别来解决。具体来说,您可以使用SELECT ... FOR UPDATE语句在更新表2之前锁定对应的课程记录,然后再进行更新操作。这样可以确保在同一时间只有一个客户端能够修改对应的记录,从而避免了竞态条件的发生。
    ·
    另外,您还可以考虑将事务隔离级别设置为SERIALIZABLE,这样可以确保事务的串行化执行,从而避免了并发操作带来的问题。不过需要注意的是,这种方式会降低并发性能,因此需要根据实际情况进行权衡。
    ·
    以下是使用SELECT ... FOR UPDATE语句的示例代码(使用C#和MySQL.Data库):

    using MySql.Data.MySqlClient;
    
    // 建立MySQL连接
    MySqlConnection conn = new MySqlConnection("server=localhost;user=root;password=123456;database=test;");
    conn.Open();
    
    // 开始事务
    MySqlTransaction trans = conn.BeginTransaction();
    
    try
    {
        // 执行SELECT ... FOR UPDATE语句
        MySqlCommand cmd = new MySqlCommand("SELECT * FROM 表2 WHERE 代码=@代码 FOR UPDATE", conn, trans);
        cmd.Parameters.AddWithValue("@代码", 课程代码);
        MySqlDataReader reader = cmd.ExecuteReader();
    
        // 处理查询结果
        if (reader.HasRows)
        {
            reader.Read();
            int 已报人数 = reader.GetInt32("已报人数");
            int 招生人数 = reader.GetInt32("招生人数");
    
            if (已报人数 < 招生人数)
            {
                // 如果已报人数小于招生人数,则执行UPDATE语句
                reader.Close();
                cmd.CommandText = "UPDATE 表2 SET 已报人数=已报人数+1 WHERE 代码=@代码";
                int rowsAffected = cmd.ExecuteNonQuery();
    
                if (rowsAffected > 0)
                {
                    // 如果UPDATE语句执行成功,则执行INSERT语句
                    cmd.CommandText = "INSERT INTO 表1 (代码, 姓名ID, 日期) VALUES (@代码, @姓名ID, @日期)";
                    cmd.Parameters.AddWithValue("@姓名ID", 姓名ID);
                    cmd.Parameters.AddWithValue("@日期", 报名时间);
                    cmd.ExecuteNonQuery();
                    trans.Commit();
                }
                else
                {
                    // 如果UPDATE语句执行失败,则回滚事务
                    trans.Rollback();
                }
            }
            else
            {
                // 如果已报人数等于或大于招生人数,则回滚事务
                reader.Close();
                trans.Rollback();
            }
        }
        else
        {
            // 如果没有查询到相关记录,则回滚事务
            reader.Close();
            trans.Rollback();
        }
    }
    catch (Exception ex)
    {
        // 出现异常时,回滚事务
        trans.Rollback();
        Console.WriteLine(ex.Message);
    }
    
    // 关闭连接
    conn.Close();
    
    
    

    在这个示例代码中,我们首先建立了MySQL连接,并开启了一个事务。然后,我们执行了一个SELECT ... FOR UPDATE语句,锁定了表2中代码为指定课程代码的记录,防止其他事务对该记录进行操作。接下来,我们根据查询结果进行判断,如果已报人数小于招生人数,则执行UPDATE语句和INSERT语句,如果已报人数等于或大于招生人数,则回滚事务。最后,我们关闭了MySQL连接。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(2条)

报告相同问题?

问题事件

  • 系统已结题 3月24日
  • 已采纳回答 3月16日
  • 创建了问题 3月12日

悬赏问题

  • ¥20 关于#stm32#的问题:需要指导自动酸碱滴定仪的原理图程序代码及仿真
  • ¥20 设计一款异域新娘的视频相亲软件需要哪些技术支持
  • ¥15 stata安慰剂检验作图但是真实值不出现在图上
  • ¥15 c程序不知道为什么得不到结果
  • ¥40 复杂的限制性的商函数处理
  • ¥15 程序不包含适用于入口点的静态Main方法
  • ¥15 素材场景中光线烘焙后灯光失效
  • ¥15 请教一下各位,为什么我这个没有实现模拟点击
  • ¥15 执行 virtuoso 命令后,界面没有,cadence 启动不起来
  • ¥50 comfyui下连接animatediff节点生成视频质量非常差的原因