【问题标题】:PHP & MySQL: How to create unique `year-month-number` keys?PHP & MySQL:如何创建唯一的“年-月-号”键?
【发布时间】:2018-05-07 17:28:46
【问题描述】:

我必须为发票创建唯一 ID。每个发票 ID 的格式为 year-month-number,其中包含发票的当前年份和月份以及一个从 1 开始的递增数字(不允许有间隙)。

例如,如果我们在 2017 年 1 月有 4 张发票,我将有以下 4 个发票 ID:

2017-1-1
2017-1-2
2017-1-3
2017-1-4

现在我想创建一个创建这些唯一 ID 的应用。特别是我想确保即使 2 个人同时请求发票号码,他们也应该得到不同的 ID。

我正在使用 InnoDB,我有下表

year  | month  | number | 
------------------------
2017  | 7      | 2      | 
2017  | 6      | 5      |
2017  | 5      | 6      |

如果尚未为year-month 对创建发票,则数据库中没有条目。主键是 year-month 对,numberauto increment index

假设我会像这样计算下一个发票 ID:

$stmt = $db->prepare('INSERT INTO book(year,month,number) 
                      VALUES (?,?,1) 
                      ON DUPLICATE KEY UPDATE number= LAST_INSERT_ID(number+1)');
$stmt->bind_param('ii', $year, $month);
$stmt->execute();
echo 'Next invoice id: ' . $year . '-' . $month . - . $db->insert_id;

解释:$db->insert_id; 返回列号,因为它的 auto increment columnLAST_INSERT_ID(number+1) 增加了最后插入的 number(也可能是由不同的用户?我不确定,我有问题在文档http://dev.mysql.com/doc/refman/5.7/en/information-functions.html#function_last-insert-id 中找到它)

这段代码真的有效吗,或者如果人们同时执行这段代码,它可能会创建多个相同的 id?

编辑 假设当前月/年数为 5。 为什么不能 2 个人同时计算发票,以便两个查询同时将数字 5 升级到 6?在这种情况下,他们都会得到发票 ID '2017-11-6' 对吗?

【问题讨论】:

  • 你没有人工智能专栏吗?如果没有,您应该这样做,因为这会自动使所有记录唯一。
  • @Fred-ii- number 字段是 AI 列,但我会通过更新增加它。
  • 你不应该为它增加AI,它会自己增加。我觉得我没有完全理解你想在这里做什么。您可以将mysqli_error($db) 添加到查询中并查看返回的内容吗?
  • @Fred-ii- 你是对的,这是使用AI 字段的一种非常不寻常的方式。但是字段number 只是一个AI 键,因此它会被$db->insert_id; 返回。事实上,它甚至不是独一无二的。可以同时是数据库中的2017-7-22017-8-2。但是2017-7-22017-7-3 是不可能的。我从这里 (stackoverflow.com/a/38544279/2311074) 发现了这个技巧,但我只是不确定它是否会给两个不同的用户提供相同的发票 ID。
  • Hm....... - 001 等与number 列相关吗? and number is an auto increment index 不会以前导零递增,因此您可能必须找到另一种方法来做到这一点(我以前在某处看到过)并为其添加另一列,id 作为 AI 并删除/ ALTER 当前的 AI'd 列。也许我没有收到您的问题/相关专栏和00X_integer,所以我可能对此没有更多帮助,对不起亚当。希望我能在这方面提供更多帮助。看看其他人是否可以提供(另一个)帮助。

标签: php mysql innodb auto-increment


【解决方案1】:

对于这样的问题,您可以尝试一下——打开两个终端窗口并使用 mysql 客户端。

mysql1> select * from book;
+------+-------+--------+
| year | month | number |
+------+-------+--------+
| 2017 |     5 |      5 |
+------+-------+--------+

在两个并发会话中启动事务:

mysql1> begin;

mysql2> begin;

会话 1 执行 IODKU 并增加数字(但尚未提交,因为 begin 隐含地使我们退出自动提交模式):

mysql11> insert into book values (2017, 5, 0) 
  on duplicate key update number = last_insert_id(number+1);

mysql1> select * from book;
+------+-------+--------+
| year | month | number |
+------+-------+--------+
| 2017 |     5 |      6 |
+------+-------+--------+

由于可重复读取事务隔离,会话 2 仍会看到原始数值。但是一旦它尝试进行自己的增量,它就会等待,因为会话 1 仍然锁定了行。

mysql2> select * from book;
+------+-------+--------+
| year | month | number |
+------+-------+--------+
| 2017 |     5 |      5 |
+------+-------+--------+
mysql12> insert into book values (2017, 5, 0) 
  on duplicate key update number = last_insert_id(number+1);
-- waits for lock

在会话 1 中提交:

mysql1> commit;

现在会话 2 中的 IODKU 完成了,我们可以看到它第二次增加了数字:

mysql2> select * from book;
+------+-------+--------+
| year | month | number |
+------+-------+--------+
| 2017 |     5 |      7 |
+------+-------+--------+

【讨论】:

  • 请注意,这种方式不会在第一次插入时设置LAST_INSERT_ID。要解决它应该是insert into book values (2017, 5, last_insert_id(1)) ...
  • 谢谢,这也是对您 2016 年 7 月的最后回答的一个很好的补充:P。我想我需要更深入地了解为什么$stmt = $db->prepare ... $stmt->executemysql1> begin; ... mysql1> commit; 相同。但可以肯定的是 - 如果没有 innodb,这将无法工作,对吧? - 实际上现在我可以测试它了:D.
  • @Adam 对于与mysqli 的交易,您应该使用mysqli::begin_transactionmysqli::commit
  • @PaulSpiegel 赞你!您是否还知道一些涵盖此类内容并且比文档更易于阅读的基本文献?
  • @Adam,我不是从书中学到的。首先,我获得了计算机科学学位。然后我阅读了很多文档。然后我用逻辑创建实验来测试我的理解。然后我在互联网上回答了像你这样的问题 20 年。这需要时间,但没有更好的方法来学习一门学科。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多