如何填补自增字段中的“空洞”?

Posted

技术标签:

【中文标题】如何填补自增字段中的“空洞”?【英文标题】:How to fill in the "holes" in auto-increment fields? 【发布时间】:2010-12-22 21:40:38 【问题描述】:

我已经阅读了一些关于此的帖子,但没有一篇涉及此问题。

我想这是不可能的,但我还是会问。

我有一个包含 50.000 多个寄存器的表。这是一张旧表,其中发生了各种插入/删除操作。

也就是说,大约 300 个寄存器中存在各种“漏洞”。即:..., 1340, 1341, 1660, 1661, 1662,...

问题是。有没有一种简单/容易的方法来让新的插件填补这些“洞”?

【问题讨论】:

考虑一下:shoes 有 id 1,shirts 有 id 2,jackets 有 id 3。您决定删除 shirts,然后决定添加 pants。如果你确实填补了空白,那么在短时间内,搜索引擎会将那些正在寻找衬衫的人引导到example.com/product?id=2,即...pants @HelloWorld 这点很好。尽管这些天搜索引擎更新得相当快,但根据填补漏洞的速度,这可能会或可能不会成为阻碍因素。此外,如果内部 CRM 系统等需要填补空白,则搜索引擎无法到达该内部区域,因此在这种情况下,搜索引擎的考虑将不适用。 【参考方案1】:

我同意@Aaron Digulla 和@Shane N 的观点。这些差距毫无意义。如果他们确实有意义,那就是有缺陷的数据库设计。期间。

话虽如此,如果您绝对需要填补这些漏洞,并且您至少运行 mysql 3.23,您可以利用 TEMPORARY TABLE 创建一组新的 ID。这里的想法是您将按顺序选择所有当前 ID 到临时表中:

CREATE TEMPORARY TABLE NewIDs
(
    NewID INT UNSIGNED AUTO INCREMENT,
    OldID INT UNSIGNED
)

INSERT INTO NewIDs (OldId)
SELECT
    Id
FROM
    OldTable
ORDER BY
    Id ASC

由于 NewId 列的 AUTO INCREMENT 属性,这将为您提供一个将旧 Id 映射到本质上将是顺序的全新 Id 的表。

完成此操作后,您需要更新对“OldTable”中 Id 的任何其他引用以及它使用的任何外键。为此,您可能需要删除您拥有的任何外键约束,将表中的任何引用从 OldId 更新为 NewId,然后重新建立外键约束。

但是,我认为您不应该这样做任何,只需了解您的 Id 字段的存在仅用于引用记录,并且应该 > 有任何特定的相关性。

更新:添加更新 ID 的示例

例如:

假设您有以下 2 个表架构:

CREATE TABLE Parent
(
    ParentId INT UNSIGNED AUTO INCREMENT,
    Value INT UNSIGNED,
    PRIMARY KEY (ParentId)
)

CREATE TABLE Child
(
    ChildId INT UNSIGNED AUTO INCREMENT,
    ParentId INT UNSIGNED,
    PRIMARY KEY(ChildId),
    FOREIGN KEY(ParentId) REFERENCES Parent(ParentId)
)

现在,差距出现在您的父表中。

为了更新您在 Parent 和 Child 中的值,您首先使用映射创建一个临时表:

CREATE TEMPORARY TABLE NewIDs
(
    Id INT UNSIGNED AUTO INCREMENT,
    ParentID INT UNSIGNED
)

INSERT INTO NewIDs (ParentId)
SELECT
    ParentId
FROM
    Parent
ORDER BY
    ParentId ASC

接下来,我们需要告诉 MySQL 忽略外键约束,这样我们才能正确更新我们的值。我们将使用以下语法:

SET foreign_key_checks = 0;

这会导致 MySQL 在更新值时忽略外键检查,但仍会强制使用正确的值类型(有关详细信息,请参阅 MySQL reference)。

接下来,我们需要使用新值更新父表和子表。我们将为此使用以下 UPDATE 语句:

UPDATE
    Parent,
    Child,
    NewIds
SET
    Parent.ParentId = NewIds.Id,
    Child.ParentId = NewIds.Id
WHERE
    Parent.ParentId = NewIds.ParentId AND
    Child.ParentId = NewIds.ParentId

我们现在已将所有 ParentId 值正确更新为临时表中新的有序 Id。完成后,我们可以重新进行外键检查以保持引用完整性:

SET foreign_key_checks = 1;

最后,我们将删除临时表以清理资源:

DROP TABLE NewIds

就是这样。

【讨论】:

我不知道您可以在 UPDATE 中对多个表进行联接。非常酷,谢谢!【参考方案2】:

您需要此功能的原因是什么?您的数据库应该可以很好地处理这些间隙,如果您接近密钥的最大大小,只需将其设为无符号或更改字段类型。

【讨论】:

另一个问题:当达到自动增量值的最大大小时会发生什么?它会溢出并且 - 当设置为 unsigned - 以 0(或 1)重新开始? @johk95 答案是mysql抛出错误,插入操作根本失败。 @ZoltánSchmidt 这不是一个愚蠢的问题,我知道它可以是美学的。然而,通常数据库是应用程序/程序的基础设施的一部分,因此应用程序将使事物看起来美观(即,计算“排名”并显示它而不是 id)。认为数据库需要漂亮就像说你想给你家墙上的螺柱涂漆一样。 这不能回答问题,也不应该是公认的答案。不管实用与否,这个问题都有一个正确的答案。 @BenjaminKohl 是正确的。这不是正确的答案。它假设应该将具有外键的其他四十个(随机数)表的字段数据类型更改为原始键。也可以在开发环境中最大化他的专栏,例如用于性能基准测试,而无需更新生产数据库。此外,说我们不应该因为我们拥有足够高效的数据库而实现效率,是弄巧成拙的。 “有没有简单/容易的方法”的答案是直接的“不”。正确的方法将取决于 OP 的确切环境。【参考方案3】:

您通常不需要关心差距。如果您要到达 ID 数据类型的末尾,则 ALTER 表以升级到下一个最大的 int 类型应该相对容易。

如果您绝对必须开始填补空白,这里有一个返回最低可用 ID 的查询(希望不要太慢):

SELECT MIN(table0.id)+1 AS newid
FROM table AS table0
LEFT JOIN table AS table1 ON table1.id=table0.id+1
WHERE table1.id IS NULL

(如果您需要并发插入工作,请记住使用事务和/或捕获重复的键插入。)

【讨论】:

一个小修正:如果表为空,此查询将返回 NULL 而不是 1。快速解决方法是使用 SELECT IFNULL(MIN(table0.id), 0) + 1 ... 【参考方案4】:
INSERT INTO prueba(id) 
VALUES (
(SELECT IFNULL( MAX( id ) , 0 )+1 FROM prueba target))

IFNULL 用于在零行计数时跳过 null

为跳过错误mysql添加目标“错误子句FROM)

【讨论】:

这只会在表的末尾插入一行,ID 为 max ID + 1。【参考方案5】:

有一个简单的方法,但效果不佳:尝试使用 id 插入,如果失败,请尝试下一个。

或者,选择一个 ID,当您没有得到结果时,使用它。

如果您正在寻找一种方法来告诉数据库自动填补空白,那么这是不可能的。此外,它不应该是必要的。如果你觉得你需要它,那么你就是在滥用内部技术密钥,但它的唯一目的是:允许你加入表。

[EDIT] 如果这不是主键,那么你可以使用这个更新语句:

update (
    select *
    from table
    order by reg_id -- this makes sure that the order stays the same
)
set reg_id = x.nextval

其中x 是您必须创建的新序列。这将重新编号保留顺序的所有现有元素。如果您有外键约束,这将失败。如果您在没有外键约束的任何地方引用这些 ID,它会损坏您的数据库。

请注意,在下一次插入期间,除非您重置标识列,否则数据库将创建一个巨大的间隙。

【讨论】:

数据库自动填补空白是可能的,只是没有实现。当空间不足时,可能需要填充间隙。键的唯一目的是用于 O(log n) SELECTs。您可以在不使用密钥的情况下加入。 O(log n) SELECT 有利于非连接表(单表查询)。【参考方案6】:

正如其他人所说,这无关紧要,如果确实如此,那么您的数据库设计就有问题。但就我个人而言,我只是希望它们井井有条!

这里有一些 SQL 将以相同的顺序重新创建您的 ID,但没有间隙。

首先在temp_id 字段(您需要创建该字段)中完成,因此您可以在覆盖旧 ID 之前看到这一切都很好。酌情替换Tblid

SELECT @i:=0;
UPDATE Tbl
JOIN
(
    SELECT id
    FROM Tbl
    ORDER BY id
) t2
ON Tbl.id = t2.id
SET temp_id = @i:=@i+1;

您现在将拥有一个 temp_id 字段,其中包含您所有闪亮的新 ID。您可以通过以下方式让它们存活:

UPDATE Tbl SET id = temp_id;

然后删除您的temp_id 列。

我必须承认我不太确定它为什么会起作用,因为我本以为引擎会抱怨重复 ID,但在我运行它时却没有。

【讨论】:

“没关系,如果有,那么你的数据库设计有问题。”在大多数情况下这可能是正确的,但并非所有情况。这不是事实。另一方面,您的解决方案非常聪明,我喜欢它。尽管它运行了两个影响整个表的查询,然后是一个表操作,表应该被锁定以防止竞争条件。锁定表 Tbl 写入;然后是你的两个查询,然后是 ALTER TABLE Tbl AUTO_INCREMENT = SELECT (COUNT(*) + 1) FROM Tbl;然后解锁表格;。考虑将这些添加到您的答案中。【参考方案7】:

您可能想清理优先级列中的空白。 下面的方法将为优先级提供一个自动递增字段。 同一个表上的额外左连接将确保它以与(在这种情况下)优先级相同的顺序添加

SET @a:=0;
REPLACE INTO footable
 (id,priority)
    (
    SELECT tbl2.id, @a 
    FROM footable as tbl
    LEFT JOIN footable as tbl2 ON tbl2.id = tbl.id  
    WHERE (select @a:=@a+1)
    ORDER BY tbl.priority
)

【讨论】:

以上是关于如何填补自增字段中的“空洞”?的主要内容,如果未能解决你的问题,请参考以下文章

如何从表中检索自增字段名称?

在Oracle中如何实现字段内的值自增呢?

SQL Server 2000中如何设置自增主键?

pgsql字段自增

SQL 一个表只有一个自增的主键字段,如何插入

DB2自增字段