【问题标题】:Make sure a count(*) select does not return the same value for two callers确保 count(*) 选择不会为两个调用者返回相同的值
【发布时间】:2022-01-26 11:34:24
【问题描述】:

在我们的网络应用程序 (Java/JDBC) 中,我们使用 MySQL,我们需要它来存储个人支付账单。每张账单必须有一个唯一的账单编号。目前,在存储新票据时,新票据的票据编号通过以下SELECT语句计算:

SELECT COUNT(*) FROM bills;

我的问题:我们如何确保没有两张账单获得相同的账单编号?

【问题讨论】:

标签: mysql sql select count


【解决方案1】:

AUTO_INCREMENT 是实现目标的“正确”方式。

如果删除了一行,COUNT(*) 将不起作用!

这是一种让你的尝试奏效的方法:

START TRANSACTION;
SELECT MAX(bill_num)+1 FROM Bills  FOR UPDATE;
...
INSERT ... INTO Bills;
COMMIT;

使用事务和FOR UPDATE 的组合可以防止另一个连接干扰。

【讨论】:

  • 代码 sn-p 看起来很有希望。但是,您能否为 ... 椭圆指定一些虚拟数据?另外,我应该如何使用 JDBC 调用 START TRANSACTION?
  • @coderodde 这个代码 sn-p 看起来不太乐观。看起来很有希望的是AUTO_INCREMENT。这段代码 sn -p 会造成并发瓶颈。
  • SELECT MAX(bill_num)+1 FROM Bills FOR UPDATE; ??该查询必须锁定哪些行以进行更新? PS。 START TRANSACTION; 没有精确的先前 SET TRANSACTION 指定可能会造成混淆 - 默认值可能因服务器而异。
  • @Akina - 我认为这将锁定表的最后一行,这应该足以阻止来自另一个连接的等效事务。 SET TRANSACTION 是什么?事务隔离通常不会被用户触及;我假设默认值。
  • 我认为这会锁定表的最后一行 不。 MAX() 会导致隐式 GROUP BY - 因此没有源行与输出行相关。不要忘记锁定(如果使用)是索引记录锁定,而不是表体记录锁定。 SET TRANSACTION 是什么? dev.mysql.com/doc/refman/8.0/en/set-transaction.html
【解决方案2】:

您可以对新账单记录进行 3 阶段插入:

  1. 使用 auto_increment 作为其 id(主键)创建新记录。
  2. 读回记录以获取它的 ID。 有了这个 ID,您就可以计算出您的帐单号码。
  3. 用账单号更新记录。

但请记住始终在帐单编号列上设置唯一约束!

如果这不是一个选项,您必须开发自己的序列生成器以获得唯一编号。从 mySql 5 开始,您可以将其实现为数据库中的存储过程/函数。

【讨论】:

    【解决方案3】:

    这里有很多建议。

    1. AUTO_INCREMENT 有效。但是,如果您打算将其用于面向公众的数据,我建议您不要使用它,因为它很容易猜测下一个账单号码;只需将帐单编号增加 1。可能存在安全问题。
    2. COUNT(*) 不起作用。当你有很多记录时,性能会受到影响,如果有删除,那么COUNT(*)实际上可能会倒退,你会得到相同的账单号。

    在不知道单号要求的情况下,我会选择 P.Salmon 推荐的UUID 路线。这样,不仅没有人可以猜出下一个账单号码,而且也很容易做到。插入 bills 表将类似于:

    INSERT INTO bills VALUES (UUID(), ...)
    

    或者如果你有 AUTO_INCREMENT 字段,你可能想做类似的事情

    START TRANSACTION;
    INSERT INTO bills VALUES (NULL, UUID(), ...);
    SELECT LAST_INSERT_ID(); -- To get the last inserted id
    COMMIT;
    

    如果 UUID 字符串不适合您,您可能需要执行替代方案,例如附加客户的号码和/或生成类似于信用卡/礼品卡生成号码的编号方案。

    如果账单号不是敏感信息,那么最简单的方法就是使用AUTO_INCREMENT作为您的账单号。

    【讨论】:

      【解决方案4】:

      如果您想在不使用自动增量等的情况下为账单创建唯一编号,一种方法是使用 Java 函数 System.currentTimeMillis() 将时间戳转换为账单 ID,除非您的账单是以每毫秒超过一个的速度生成;)

      【讨论】:

        【解决方案5】:

        SELECT COUNT(*) 可以计算两个票据相同的票据编号,如果不想计算,可以设置票据编号的唯一索引

        COUNT(arg) 本身就是一个 MySQL 函数。如果括号中的 arg(列或整行)不为 NULL,则计算 COUNT ++,否则不计算该行。有关详细信息,请转到“Evaluate_join_record 且列为空”部分。

        【讨论】:

          猜你喜欢
          • 2011-02-27
          • 1970-01-01
          • 1970-01-01
          • 2021-08-30
          • 2021-01-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多