每个字段的唯一序列 php 和 mysql
unique sequence per field with php and mysql
在给定的 table 中,我必须字段:pos(销售点)和 voucher_number。
它们都分组在一个唯一索引中
我需要每个 pos 有一个唯一的序列,例如
ID
date
pos
voucher_number
1
2021-01-01 11:45:37
1
1
2
2021-01-01 17:22:45
1
2
3
2021-01-02 15:08:02
2
1
4
2021-01-02 15:08:02
1
3
5
2021-01-03 10:37:24
2
2
6
2021-01-03 10:37:24
3
1
7
2021-01-03 10:37:24
1
4
不同的用户可以同时创建不同的凭证,所以我关心的是如何保证顺序保持自然顺序,不重复或跳过数字
我正在处理的代码包含在一个转换中,我正在考虑做这样的事情(伪):
//At this point a transaction has already been started
function save_voucher($voucher_data)
{
//Notice the SELECT ... FOR UPDATE
$max_voucher_number = select max(voucher_number) as max_voucher_number from vouchers where pos = $voucher_data["pos"] for update;
$voucher_data["voucher_number"] = $max_voucher_number + 1;
//I can't alter save_voucher() in parent class
//But inside it would perform something like:
//insert into (pos, voucher_number) values ($voucher_data["pos"], $voucher_data["voucher_number"]);
return parent::save_voucher($voucher_data);
}
//After the method execution, a commit or rollback happens;
或者甚至是这样的:
//At this point a transaction has already been started
function save_voucher()
{
//I can't alter save_voucher() in parent class
$new_voucher_id = parent::save_voucher();
//Now, after a voucher had been created I could get the max sequence
//Notice the SELECT ... FOR UPDATE
$max_voucher_number = select max(voucher_number) as max_voucher_number from vouchers where pos = $pos for update;
//Then, I could update that voucher
update vouchers set voucher_number = $max_voucher_number + 1 where id = $new_voucher_id;
return $new_voucher_id;
}
//After the method execution, a commit or rollback happens;
以上是否可以保证我的每个 pos 都有一个唯一的序列?可能的并发会以任何方式影响序列吗?
我将 MySQL 与所有 table 一起使用 InnoDB
答案是肯定的,我可以安全地获得每个 pos 的唯一序列。
我已经 运行 进行了两种测试,以了解当两个用户同时尝试做同样的事情时会发生什么。
在我继续之前,我应该提到我必须摆脱唯一索引。如果存在唯一性,我会收到错误(死锁)并且第二个连接失败。我将在另一个 post...
中处理这个问题
测试#1
我将测试用例隔离在名为 test_a.php
的文件中,并将其复制到 test_b.php
中。然后我设置了一个这样的cron
*/5 * * * * cd /path/to/test/ && php test_a.php >> /var/log/test_a.log 2>&1
*/5 * * * * cd /path/to/test/ && php test_b.php >> /var/log/test_b.log 2>&1
这将按预期生成正确的序列。我什至可以将 sleep()
放在 test_a 上,然后 test_b 会等待。我不确定作业 运行 是否在纳秒内同时进行,但它们让我更接近实际情况。
测试 #2
我复制了 :使用两个终端实例,每个实例都打开了 MySQL 客户端。
上 first terminal
你 运行 SQL:
START TRANSACTION;
SELECT ... FOR UPDATE;
# Do not COMMIT to keep the transaction alive
关于second terminal
同样的事情:
START TRANSACTION;
SELECT ... FOR UPDATE;
# Do not COMMIT to keep the transaction alive
由于您在 first terminal
上创建了一个 select 更新,上面的查询将等待 select 更新事务提交,否则它将超时。
返回 first terminal
并提交事务:
COMMIT;
检查second terminal
,您将看到查询已执行。
虽然这个例子不是并发的,但是它展示了SELECT...FOR UPDATE
的行为
在给定的 table 中,我必须字段:pos(销售点)和 voucher_number。
它们都分组在一个唯一索引中
我需要每个 pos 有一个唯一的序列,例如
ID | date | pos | voucher_number |
---|---|---|---|
1 | 2021-01-01 11:45:37 | 1 | 1 |
2 | 2021-01-01 17:22:45 | 1 | 2 |
3 | 2021-01-02 15:08:02 | 2 | 1 |
4 | 2021-01-02 15:08:02 | 1 | 3 |
5 | 2021-01-03 10:37:24 | 2 | 2 |
6 | 2021-01-03 10:37:24 | 3 | 1 |
7 | 2021-01-03 10:37:24 | 1 | 4 |
不同的用户可以同时创建不同的凭证,所以我关心的是如何保证顺序保持自然顺序,不重复或跳过数字
我正在处理的代码包含在一个转换中,我正在考虑做这样的事情(伪):
//At this point a transaction has already been started
function save_voucher($voucher_data)
{
//Notice the SELECT ... FOR UPDATE
$max_voucher_number = select max(voucher_number) as max_voucher_number from vouchers where pos = $voucher_data["pos"] for update;
$voucher_data["voucher_number"] = $max_voucher_number + 1;
//I can't alter save_voucher() in parent class
//But inside it would perform something like:
//insert into (pos, voucher_number) values ($voucher_data["pos"], $voucher_data["voucher_number"]);
return parent::save_voucher($voucher_data);
}
//After the method execution, a commit or rollback happens;
或者甚至是这样的:
//At this point a transaction has already been started
function save_voucher()
{
//I can't alter save_voucher() in parent class
$new_voucher_id = parent::save_voucher();
//Now, after a voucher had been created I could get the max sequence
//Notice the SELECT ... FOR UPDATE
$max_voucher_number = select max(voucher_number) as max_voucher_number from vouchers where pos = $pos for update;
//Then, I could update that voucher
update vouchers set voucher_number = $max_voucher_number + 1 where id = $new_voucher_id;
return $new_voucher_id;
}
//After the method execution, a commit or rollback happens;
以上是否可以保证我的每个 pos 都有一个唯一的序列?可能的并发会以任何方式影响序列吗?
我将 MySQL 与所有 table 一起使用 InnoDB
答案是肯定的,我可以安全地获得每个 pos 的唯一序列。
我已经 运行 进行了两种测试,以了解当两个用户同时尝试做同样的事情时会发生什么。
在我继续之前,我应该提到我必须摆脱唯一索引。如果存在唯一性,我会收到错误(死锁)并且第二个连接失败。我将在另一个 post...
中处理这个问题测试#1
我将测试用例隔离在名为 test_a.php
的文件中,并将其复制到 test_b.php
中。然后我设置了一个这样的cron
*/5 * * * * cd /path/to/test/ && php test_a.php >> /var/log/test_a.log 2>&1
*/5 * * * * cd /path/to/test/ && php test_b.php >> /var/log/test_b.log 2>&1
这将按预期生成正确的序列。我什至可以将 sleep()
放在 test_a 上,然后 test_b 会等待。我不确定作业 运行 是否在纳秒内同时进行,但它们让我更接近实际情况。
测试 #2
我复制了
上 first terminal
你 运行 SQL:
START TRANSACTION;
SELECT ... FOR UPDATE;
# Do not COMMIT to keep the transaction alive
关于second terminal
同样的事情:
START TRANSACTION;
SELECT ... FOR UPDATE;
# Do not COMMIT to keep the transaction alive
由于您在 first terminal
上创建了一个 select 更新,上面的查询将等待 select 更新事务提交,否则它将超时。
返回 first terminal
并提交事务:
COMMIT;
检查second terminal
,您将看到查询已执行。
虽然这个例子不是并发的,但是它展示了SELECT...FOR UPDATE