繁体   English   中英

每个字段的唯一序列 php 和 mysql

[英]unique sequence per field with php and mysql

在给定的表格中,我必须填写以下字段:pos(销售点)和凭证编号。

它们都被分组在一个唯一索引中

例如,我需要每个 pos 有一个唯一的序列

ID 日期 位置 凭证号码
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 与所有使用 InnoDB 的表

答案是肯定的,我可以安全地获得每个 pos 的唯一序列。

我已经运行了两种测试,以查看两个用户同时尝试做同样的事情会发生什么。

在我继续之前,我应该提到我必须摆脱唯一索引。 如果唯一存在,我会收到错误(死锁)并且第二个连接失败。 我将在另一篇文章中处理这个问题......

测试#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

这将按预期生成正确的序列。 我什至可以在 test_a 上放一个sleep() ,而 test_b 会等待。 我不确定这些作业是否在纳秒内同时运行,但它们让我更接近实际情况。

测试#2

我复制了Franciso 的回答:使用两个终端实例,每个实例都打开 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 提交,否则它将超时。

Go 回到第first terminal并提交事务:

COMMIT;

检查second terminal ,您将看到查询已执行。

尽管此示例不是并发的,但它显示了SELECT...FOR UPDATE的行为

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM