我希望在 API 中混合表锁和事务。我选择了 MySQL 5.7 作为存储引擎,尽管我并不特别致力于任何特定的 RDBMS。在深入研究之前,我有兴趣了解我当前的设计是否适合 MySQL。
在我的设计中,我有三个表:
expenditure
(id
,amount
,title
)donation
(id
,amount
,title
)contribution
(id
,amount
,expenditure_id
,donation_id
)
如您所见,这是两个表之间的简单多:多,contribution
作为桥接表。我已经设置了约束,以便强制执行这种关系。
该应用程序基本上是一个慈善机构的会计程序 - 管理员创建捐赠条目(来自捐赠者的钱)和支出(用于慈善目的的钱)。管理员有时会通过在桥接表中创建条目来将捐款分配给支出。大额捐款可以用于几笔支出,大笔支出会消耗几笔捐款。
我感兴趣的是如何在 MySQL 中创建这些桥接条目。我想我会有一个像这样的伪代码的函数:
function addContributions(int expenditureId, array contributions)
因此,假设我的支出为 5000,并且我有两笔捐款(id 1 和 2),金额分别为 3000 和 2500。我想要 3000(所有第一次捐款)和 2000(部分第二次捐款)的捐款。贡献行如下所示:
[
{expenditure_id: 1, donation_id: 1, amount: 3000},
{expenditure_id: 1, donation_id: 2, amount: 2000}
]
我有一些相关的观察:
- 我有一些验证要做,例如确保捐款没有附加到其他地方
- 我希望能够在出现错误的情况下回滚
- 我希望我的验证在编写这些记录期间保持真实,即防止在检查数据库和编写记录之间出现竞争条件
- 我认为这意味着我想在中央表上写一个表锁
这是我的代码粗略草图(节点,但语言无关紧要):
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() {
}
}
该手册给出了这个建议:
将 LOCK TABLES 和 UNLOCK TABLES 与事务表(例如 InnoDB 表)一起使用的正确方法是使用 SET autocommit = 0(不是 START TRANSACTION)开始事务,然后是 LOCK TABLES,并且在提交事务之前不要调用 UNLOCK TABLES明确的
但是它也说(在同一页面上):
LOCK TABLES 不是事务安全的,并且在尝试锁定表之前隐式提交任何活动事务
所以,我认为这些东西是不相容的。我想使用事务以便在出现问题的情况下能够回滚,这样数据库就不会处于不一致的状态。我还想使用写锁,以便我的验证在表被解锁之前保持正确,否则可能会发生竞争条件,从而超支捐赠(由于另一个用户分配了相同的捐赠之一)。
但是如果LOCK TABLES
提交一个开放的事务,那么我会不会有这种情况(时间向下流动)?
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 |
我想知道这里发生了什么,lock tables
因为用户 2 对用户 1 的事务进行了隐式提交,rollback
用户 1 没有进行完全回滚,因为已经发出了一些提交。
或者用户 2 的锁表是否基于另一个会话具有表锁而强制等待,并且它暂停操作直到用户 1 释放锁?