在MySQL 5.7中混合事务和表锁是否不切实际?

在MySQL 5.7中混合事务和表锁是否不切实际?,mysql,sqltransaction,table-locking,Mysql,Sqltransaction,Table Locking,我希望在API中混合使用表锁和事务。我选择MySQL 5.7作为存储引擎,尽管我并不特别致力于任何特定的RDBMS。在深入研究之前,我很想看看我目前的设计是否适合MySQL 在我的设计中,我有三张桌子: 支出(id,金额,标题) 捐赠(id,金额,标题) 捐款(身份证,金额,支出,捐赠) 正如您所看到的,这是一个简单的多:多在两个表之间,而contribution是桥接表。我已设置了约束,以便强制执行关系 该应用程序基本上是一个慈善机构的会计程序-管理员创建捐赠条目(来自捐赠者的资金)和支出

我希望在API中混合使用表锁和事务。我选择MySQL 5.7作为存储引擎,尽管我并不特别致力于任何特定的RDBMS。在深入研究之前,我很想看看我目前的设计是否适合MySQL

在我的设计中,我有三张桌子:

  • 支出
    id
    金额
    标题
  • 捐赠
    id
    金额
    标题
  • 捐款
    身份证
    金额
    支出
    捐赠
正如您所看到的,这是一个简单的多:多在两个表之间,而
contribution
是桥接表。我已设置了约束,以便强制执行关系

该应用程序基本上是一个慈善机构的会计程序-管理员创建捐赠条目(来自捐赠者的资金)和支出(用于慈善目的的资金)。管理员将通过在桥接表中创建条目,不时将捐款分配给支出。大额捐款可用于多项支出,而大额支出将消耗多项捐款

我感兴趣的是如何在MySQL中创建这些桥接条目。我想我会有这样一个伪代码:

function addContributions(int expenditureId, array contributions)
假设我有5000英镑的支出,我有两笔捐款(ID 1和ID 2),金额分别为3000英镑和2500英镑。我想要3000英镑(全部第一笔捐款)和2000英镑(部分第二笔捐款)。贡献行如下所示:

[
  {expenditure_id: 1, donation_id: 1, amount: 3000},
  {expenditure_id: 1, donation_id: 2, amount: 2000}
]
我有一些相关的观察:

  • 我有一些验证要做,例如确保捐款没有附在其他地方
  • 我希望在发生错误时能够回滚
  • 我希望我的验证在写入这些记录时保持正确,即在检查数据库和写入记录之间防止竞争条件
  • 我想这意味着我希望在中央表上有一个写表锁
这是我对代码的粗略描述(节点,但语言无关紧要):

手册:

将锁表和解锁表与事务表(如InnoDB表)一起使用的正确方法是使用SET autocommit=0(not START transaction)开始事务,后跟锁表,并且在显式提交事务之前不调用解锁表

然而,它也说(在同一页上):

锁定表不是事务安全的,在尝试锁定表之前隐式提交任何活动事务

所以,我认为这些东西是不相容的。我希望使用事务,以便在出现问题时能够回滚,这样数据库就不会处于不一致的状态。我还希望使用写锁,以便在表解锁之前验证保持正确,否则可能会发生竞态条件,导致捐赠超支(由于另一个用户分配了相同的捐赠)

但是如果
锁表
提交了一个打开的事务,那么我会遇到这种情况吗(时间向下流动)

我想知道这里发生了什么,用户2的
锁表
对用户1的事务执行隐式提交,而用户1的
回滚
没有执行完全回滚,因为已经发出了一些提交


或者,用户2的锁表是否在另一个会话具有表锁的基础上强制等待,并且它会暂停操作,直到用户1释放锁?

表锁不是真正为并发控制而设计的,因为这些锁将表中除一个用户外的所有用户都锁定。您可以在一次性情况下使用它们,例如特殊维护任务,以确保没有其他流程干扰您的工作

我会在支出和缴款表中添加一个未付金额字段,以指示尚未匹配的金额(是的,这是一个非规范化字段)


我将向桥接表添加insert/update/delete触发器,当您创建/修改/删除匹配条目时,触发器将通过从字段值中扣除/添加匹配值来更新未付金额字段。由于更新操作需要对正在更新的记录进行独占锁定,触发器将有效地将对未付金额字段的修改排队。触发器可以强制该值不能低于0,如果该值低于0,则会引发错误。

“锁定表”显然会给您带来麻烦。通常,交易是足够的。尝试不使用它并编写一些测试用例。某些
SELECT FOR UPDATE
可能需要对数据集的某些部分保持独占控制。您正在尝试执行“悲观锁定”。这确实会降低性能,但我会尽可能避免。我使用了“乐观锁定”,大多数情况下在小交易中都能很好地工作。谢谢你的想法@danblack。我还没有尝试过这个-代码看起来很像你在这里看到的框架代码。我非常愿意相信
锁表
会给我带来麻烦!我没有想到要更新,我会仔细考虑的。@TheImpler:啊,知道它有个名字很有用。谢谢用例对性能没有太大的需求,但我肯定值得注意这一影响。在进行事务时,重要的是使用非常适合查询的索引,以将行的隐式锁定保持在最低限度。嗯,感谢Shadow,这是非常有用的思想食粮。如果我要走触发路线,那么我大概可以打开一个交易,删除与特定支出相关的所有捐款,然后插入每个捐款。捐款会有一个触发点
export default class ContributionWriter {

  addContributions(expenditureId, contributions) {

    this.startLock();
    try {
      this.startTransaction();
      this.addContributionsWork();
      this.commit();
    }
    catch (error) {
      this.rollback();
    }
    finally {
      this.endLock();
    }
  }

  addContributionsWork() {
    // Initial validation
    this.detectInvalidContributions();
    this.detectTooManyContributions();
    this.detectOverCommittedDonations();

    // Write contribs
    this.writeContributions();
  }

  /**
   * Write the contributions, validations passed OK
   */
  writeContributions() {
    // DELETE FROM contribution WHERE id_expenditure = x
    // Loop for all contributions:
    //   INSERT INTO contribution ...
  }

  /**
   * Prevent any writes to the table(s) during the lock
   */
  startLock() {
    // LOCK TABLES contribution
  }

  /**
   * Releases the locked table(s)
   */
  endLock() {
    // UNLOCK TABLES
  }

  /**
   * Runs a `SET autocommit = 0` to start the transaction
   */
  startTransaction() {

  }

  /**
   * Runs a ROLLBACK op to cancel any pending changes
   */
  rollback() {

  }

  commit() {

  }

}
User 1                  | User 2
------------------------|------------------------
lock tables (write)     |
start tx                |
validation checks       |
insert A                |
                        | lock tables (write)
                        | start tx
                        | validation checks
insert B                |
error                   |
rollback                |
unlock tables           |