首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >我有强迫症:Z-Blog 博客主键 ID 复用指南

我有强迫症:Z-Blog 博客主键 ID 复用指南

原创
作者头像
高老师
发布2026-03-10 13:33:15
发布2026-03-10 13:33:15
780
举报

我有强迫症:Z-Blog 博客主键 ID 复用指南

作为一名强迫症患者,看到文章 ID 不连续简直抓狂!本文记录了我如何用 SQL 找回那些"消失"的 ID。

背景

我有一个运行多年的 Z-Blog 博客,累计发布了十几万篇文章。多年来,删删改改,导致主键 ID 变得支离破碎:

  • 第1篇文章 ID 是 1
  • 第2篇文章 ID 是 3(ID 2 被删除了)
  • 第3篇文章 ID 是 7(ID 4、5、6 都没了)
  • ...

更夸张的是,最大 ID 已经到了 14,692,488,但实际只有 13,500,210 条记录。这意味着有 约 119 万个 ID 被浪费了!毕竟我是一个爬虫爱好者。哈哈

作为一个强迫症,我决定要把这些 ID 找回来,让它们重新"上岗"。

问题分析

表结构

Z-Blog 的文章表是 zbp_post,主键字段是 log_ID

目标

  1. 找出从 1 到最大 ID 之间所有缺失的 ID
  2. 保存到 zbp_delete 表中
  3. 后续插入新文章时,优先使用这些可复用的 ID

难点

  • 数据量大:1400 多万条记录
  • MySQL 版本低:服务器用的是 MySQL 5.x,不支持递归 CTE(MySQL 8.0+ 才支持)
  • 不能一个一个循环查,太慢

解决方案

经过一番研究,我找到了一个高效的 SQL 方案,核心思路是:

  1. 创建一个数字辅助表:存储 1 到最大 ID 的所有数字
  2. 用 LEFT JOIN 找缺口:数字表 LEFT JOIN 文章表,找不到对应记录的就是缺失的 ID

完整 SQL 代码

代码语言:sql
复制
-- ============================================
-- Z-Blog 主键 ID 复用方案
-- 适用于 MySQL 5.x(不支持递归CTE的版本)
-- ============================================

-- 第一步:创建数字辅助表
DROP TABLE IF EXISTS numbers;
CREATE TABLE numbers (n INT PRIMARY KEY);

-- 第二步:创建存储过程,填充数字表
DELIMITER $$
CREATE PROCEDURE fill_numbers(IN max_num INT)
BEGIN
  DECLARE i INT DEFAULT 0;
  WHILE i < max_num DO
    INSERT IGNORE INTO numbers VALUES (i+1);
    SET i = i + 1000;
    INSERT IGNORE INTO numbers VALUES 
      (i),(i+1),(i+2),(i+3),(i+4),(i+5),(i+6),(i+7),(i+8),(i+9),
      (i+10),(i+11),(i+12),(i+13),(i+14),(i+15),(i+16),(i+17),(i+18),(i+19),
      (i+20),(i+21),(i+22),(i+23),(i+24),(i+25),(i+26),(i+27),(i+28),(i+29),
      (i+30),(i+31),(i+32),(i+33),(i+34),(i+35),(i+36),(i+37),(i+38),(i+39),
      (i+40),(i+41),(i+42),(i+43),(i+44),(i+45),(i+46),(i+47),(i+48),(i+49),
      (i+50),(i+51),(i+52),(i+53),(i+54),(i+55),(i+56),(i+57),(i+58),(i+59),
      (i+60),(i+61),(i+62),(i+63),(i+64),(i+65),(i+66),(i+67),(i+68),(i+69),
      (i+70),(i+71),(i+72),(i+73),(i+74),(i+75),(i+76),(i+77),(i+78),(i+79),
      (i+80),(i+81),(i+82),(i+83),(i+84),(i+85),(i+86),(i+87),(i+88),(i+89),
      (i+90),(i+91),(i+92),(i+93),(i+94),(i+95),(i+96),(i+97),(i+98),(i+99);
    SET i = i + 900;
  END WHILE;
END$$
DELIMITER ;

-- 第三步:填充数字表(替换为你的最大ID)
-- 先查一下最大ID:SELECT MAX(log_ID) FROM zbp_post;
CALL fill_numbers(15000000);

-- 第四步:创建可复用ID存储表
DROP TABLE IF EXISTS zbp_delete;
CREATE TABLE zbp_delete (
  id INT PRIMARY KEY,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 第五步:用 LEFT JOIN 找出所有缺失的ID(最关键的一步!)
INSERT INTO zbp_delete (id)
SELECT n.n 
FROM numbers n
LEFT JOIN zbp_post p ON n.n = p.log_ID
WHERE p.log_ID IS NULL;

-- 第六步:查看结果
SELECT COUNT(*) AS '可复用ID数量' FROM zbp_delete;
SELECT * FROM zbp_delete ORDER BY id LIMIT 100;

执行效果

代码语言:bash
复制
可复用ID数量: 1192278

成功找回了 119 万多个可复用的 ID

性能表现

操作

耗时

创建并填充 1500 万数字表

约 3 分钟

LEFT JOIN 找出缺失 ID

约 2 分钟

总计

约 5 分钟

对比之前用代码逐个检查的方案(预计需要几小时),效率提升了 几十倍

原理解释

为什么这个方案快?

  1. 批量插入:存储过程每次插入 1000 条记录,减少 SQL 执行次数
  2. LEFT JOIN + IS NULL:数据库引擎对 JOIN 做了深度优化,比 NOT IN 子查询快得多
  3. 主键索引:数字表和文章表都用主键关联,查询速度极快

为什么不用递归 CTE?

递归 CTE(Common Table Expression)是 MySQL 8.0 引入的功能,语法优雅:

代码语言:sql
复制
-- MySQL 8.0+ 递归CTE方案(更简洁,但不兼容低版本)
WITH RECURSIVE number_sequence AS (
  SELECT 1 AS id
  UNION ALL
  SELECT id + 1 FROM number_sequence 
  WHERE id < (SELECT MAX(log_ID) FROM zbp_post)
)
SELECT id FROM number_sequence
WHERE id NOT IN (SELECT log_ID FROM zbp_post);

但我的服务器还在用 MySQL 5.7,只能用数字辅助表的方案。

后续使用

有了 zbp_delete 表,以后插入新文章时就可以优先使用这些"回收"的 ID:

代码语言:sql
复制
-- 获取一个可复用的ID
SELECT id FROM zbp_delete ORDER BY id LIMIT 1;

-- 插入文章后,删除该ID
DELETE FROM zbp_delete WHERE id = ?;

或者在 Z-Blog 代码中修改插入逻辑,实现自动复用。

总结

作为一个强迫症患者,看到 ID 终于可以连续了,心里舒服多了!

这次优化的关键收获:

  1. SQL 比代码快:能用 SQL 解决的问题,尽量在数据库层面完成
  2. JOIN 优于 INLEFT JOIN ... IS NULLNOT IN 性能好得多
  3. 批量操作:减少单条插入,用批量操作提升效率

希望这个方案能帮到同样有强迫症的博主们!😄


本文 SQL 已在我的 Z-Blog 博客上验证通过,MySQL 版本 5.7.40

如果有更好的方案,欢迎留言讨论!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 我有强迫症:Z-Blog 博客主键 ID 复用指南
    • 背景
    • 问题分析
      • 表结构
      • 目标
      • 难点
    • 解决方案
      • 完整 SQL 代码
    • 执行效果
      • 性能表现
    • 原理解释
      • 为什么这个方案快?
      • 为什么不用递归 CTE?
    • 后续使用
    • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档