【问题标题】:C# acquire lock from mysql database for critical section of codeC# 从 mysql 数据库获取代码的关键部分的锁
【发布时间】:2018-01-04 17:16:04
【问题描述】:

我正在使用带有 MySql 数据库的 Asp.NET。

申请流程:

  1. 在 Woocommerce 中创建的订单并发送到我的应用程序
  2. 我的应用程序将 woo 订单对象转换为要添加到外部 ERP 系统的对象
  3. 在外部 ERP 系统中创建订单,我们使用该订单信息更新本地数据库,以了解创建成功

我有一段关键的代码可以在外部 ERP 资源上创建订单。同一订单的多个请求可以同时运行,因为它们是从我无法控制的外部应用程序 (woocommerce) 创建的。因此,代码的关键部分必须一次只允许输入一个请求,否则可能会创建重复订单。

重要提示:应用程序托管在具有负载均衡器的 Elastic Beanstalk 上,因此应用程序可以跨多个服务器扩展,这使得标准 C# 锁定对象不起作用。

我想创建一个可以在多个服务器/应用程序实例之间共享的锁,以便一次只有一个服务器可以获取锁并进入代码的关键部分。我找不到如何使用 MySql 和 C# 来做到这一点,所以如果有人有一个很好的例子。

以下是我如何进行单实例线程安全锁定。如何将其转换为跨多个实例的安全:

SalesOrder newOrder = new SalesOrder();         //the external order object
var databaseOrder = new SalesOrderEntity();     //local MySql database object

/*
 * Make this section thread safe so multiple threads can't try to create
 * orders at the same time 
 */
lock (orderLock)
{
    //check if the order is already locked or created.
    //wooOrder comes from external order creation application (WooCommerce)
    databaseOrder = GetSalesOrderMySqlDatabase(wooOrder.id.ToString(), originStore);

    if (databaseOrder.OrderNbr != null)
    {
        //the order is already created externally because it has an order number
        return 1;
    }
    if (databaseOrder.Locked)
    {
        //the order is currently locked and being created
        return 2;
    }

    //the order is not locked so lock it before we attempt to create externally
    databaseOrder.Locked = true;
    UpdateSalesOrderDatabase(databaseOrder);

    //Create a sales order in external system with the specified values
    newOrder = (SalesOrder) client.Put(orderToBeCreated);


    //Update the order in our own database so we know it's created in external ERP system
    UpdateExternalSalesOrderToDatabase(newOrder);

}

如果需要更多详细信息,请告诉我。

【问题讨论】:

  • 您是否只有一台服务器作为数据库服务器?或者您是否也在对数据库服务器进行负载平衡?
  • 数据库是单实例。好问题

标签: c# mysql multithreading transactions


【解决方案1】:

您可以为此使用 MySQL 的命名咨询锁定功能 GET_LOCK(name)

这在事务范围之外起作用,因此您在释放锁定之前提交或回滚数据库更改。在此处阅读更多信息:https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock

您还可以使用其他一些专用的锁定服务。例如,您可以使用共享消息队列服务来执行此操作。见https://softwareengineering.stackexchange.com/questions/127065/looking-for-a-distributed-locking-pattern

【讨论】:

    【解决方案2】:

    您需要为此使用 MySQL DBMS 事务锁。

    您不直接显示您的 DBMS 查询,所以我无法猜测它们。您仍然需要这类查询。

     START TRANSACTION;
     SELECT col, col, col FROM wooTable WHERE id = <<<wooOrderId>>> FOR UPDATE;
    
     /* do whatever you need to do */
    
     COMMIT;
    

    如果相同的&lt;&lt;&lt;wooOrderID&gt;&gt;&gt; 行被来自另一个ELB 服务器上运行的Web 服务器的另一个实例的相同查询序列命中,则该SELECT ... FOR UPDATE 查询将等待直到第一个提交。

    请注意,服务器内的多线程和临界区锁定对于解决您的问题既没有必要也不足以解决问题。为什么?

    这是不必要的,因为数据库连接首先不是线程安全的。

    这是不够的,因为您需要一个数据库级别的事务,而不是进程级别的锁。

    【讨论】:

    • 我尝试使用 C# TransactionScope() 对象在代码中创建事务,但没有成功。 DBMS 事务锁与在代码中启动事务不同吗?
    • TransactionScope + MySQL === 0 。 bugs.mysql.com/bug.php?id=70587
    【解决方案3】:

    您应该使用Transaction,它是数据库中的一个工作单元。它使您的代码不仅是原子的,而且是线程安全的。这是一个来自mysql official website的示例

    你需要的代码:

    START TRANSACTION
    
    
    COMMIT // if your transaction worked
    ROLLBACK // in case of failure 
    

    此外,我强烈建议您阅读有关事务隔离级别的信息: Mysql Transaction Isolation Levels

    如果你使用我上面写的Transaction,你的表上有一个锁,这会阻止其他查询,例如选择查询,执行,他们将等待事务结束。称为“服务器阻塞”,以防止只是密集阅读链接。

    【讨论】:

    • MySQL 进行乐观锁定。换句话说,在您明确查询表之前,您不会获得对表的锁定。仅使用事务没有帮助。 OP 必须在事务的早期使用 LOCK TABLES 以原子方式锁定多个资源。
    【解决方案4】:

    我认为使用数据库没有任何好的解决方案,除非一切都可以像另一个答案建议的那样在存储过程中整齐地完成。对于其他任何事情,我都会考虑一个具有多个写入者和一个读取器的消息队列解决方案。

    【讨论】:

      猜你喜欢
      • 2016-02-09
      • 1970-01-01
      • 2014-06-10
      • 2011-09-07
      • 2016-04-27
      • 2016-06-03
      • 1970-01-01
      • 2018-10-25
      • 2011-04-02
      相关资源
      最近更新 更多