在MySQL 5.7中混合事务和表锁是否不切实际?
我希望在API中混合使用表锁和事务。我选择MySQL 5.7作为存储引擎,尽管我并不特别致力于任何特定的RDBMS。在深入研究之前,我很想看看我目前的设计是否适合MySQL 在我的设计中,我有三张桌子:在MySQL 5.7中混合事务和表锁是否不切实际?,mysql,sqltransaction,table-locking,Mysql,Sqltransaction,Table Locking,我希望在API中混合使用表锁和事务。我选择MySQL 5.7作为存储引擎,尽管我并不特别致力于任何特定的RDBMS。在深入研究之前,我很想看看我目前的设计是否适合MySQL 在我的设计中,我有三张桌子: 支出(id,金额,标题) 捐赠(id,金额,标题) 捐款(身份证,金额,支出,捐赠) 正如您所看到的,这是一个简单的多:多在两个表之间,而contribution是桥接表。我已设置了约束,以便强制执行关系 该应用程序基本上是一个慈善机构的会计程序-管理员创建捐赠条目(来自捐赠者的资金)和支出
(支出
,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}
]
我有一些相关的观察:
- 我有一些验证要做,例如确保捐款没有附在其他地方
- 我希望在发生错误时能够回滚
- 我希望我的验证在写入这些记录时保持正确,即在检查数据库和写入记录之间防止竞争条件
- 我想这意味着我希望在中央表上有一个写表锁
锁表
提交了一个打开的事务,那么我会遇到这种情况吗(时间向下流动)
我想知道这里发生了什么,用户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 |