0

我有一个设置为 auto_increment 的唯一 PK 'id'。我有一个名为“标签”的第二个字段,它是一个字母数字字段(例如 W1000),旨在使用 PHP 逻辑在每次插入时递增。

“标签”字段可能有许多字母前缀之一,后跟递增数字。每个前缀应独立递增。例如,该表可能有 W1000 和 F1123。下一个 W 是 W1001,下一个 F 是 F1124。

当前方法(PHP 选择最大标签,插入最大标签 + 1)创建了一个竞争条件,偶尔我会得到一个重复的“标签”。我需要解决这些重复的“标签”并确保该字段是唯一的。如果有帮助,我愿意将前缀和数字分成两个字段。

实现这一目标的最佳方法是什么?

4

1 回答 1

1

避免生成重复标签值的一种方法是使用 MyISAM 表来生成唯一的序列号。MyISAM 支持您需要的 AUTO_INCREMENT 行为。

请参阅 MySQL 参考3.6.9 中的“MyISAM 注释”部分。使用 AUTO_INCREMENT

对于这种方法,您将创建一个单独的 MyISAM 表;该表的目的是生成唯一的序列号:例如

CREATE TABLE foo 
( prefix  VARHCAR(1) NOT NULL
, num     INT UNSIGNED AUTO_INCREMENT
, PRIMARY KEY (prefix, num)
) Engine=MyISAM

假设标签前缀是一个字符,余数是一个数字:

INSERT INTO foo (prefix, num)
SELECT SUBSTR(t.label,1,1) AS prefix
     , MAX(SUBSTR(t.label,2,8) AS num
  FROM mytable
 GROUP BY SUBSTR(t.label,1,1)

获取一个新的序列号,在新表中插入一行,为 prefix 提供一个值,为 num 列提供一个 NULL,然后检索为 num 列插入的值:

INSERT INTO foo (prefix,num) VALUES ('W',NULL); 
SELECT LAST_INSERT_ID();

您可以使用它来构造要用于label原始表中的列的值。

请注意,只有 MyISAM 引擎具有您想要的行为(为每个前缀分别递增 AUTO_INCREMENT 序列。)您的原始表可以是任何引擎。

这种方法避免了竞态条件,但确实引入了并发瓶颈,这是由于在 MyISAM 表上为插入而采取的排他锁。


避免竞争条件的另一种方法是在表上获得排他锁,然后执行 SELECT MAX(),然后执行插入,然后释放锁。但是这种方法引入了更多的并发瓶颈,序列化对单个资源的访问。


如果您的问题是关于识别现有的重复label值,那么此查询将为您提供具有“重复”标签的行。(这只是为每个重复的标签挑选一行。)

SELECT t.label
     , MAX(t.id)
  FROM mytable t
 GROUP BY t.label
HAVING COUNT(1) > 1

要将标签更新为唯一,您需要为这些行生成一个新标签。

在一条 SQL 语句中完成这项工作有点棘手。我试图提出一个声明,但它被破坏了,我没有时间修复它。

于 2013-03-25T02:33:17.240 回答