SQLite AUTOINCREMENT:自动生成编号,和保证不复用旧编号有什么区别
没写自增关键字,编号也会增加
给 SQLite 表声明 INTEGER PRIMARY KEY 后,插入时省略这一列,数据库通常就会自动分配整数。于是有人认为 AUTOINCREMENT 只是更明确的写法,也有人把它理解成保证永远连续的序列。两个理解都容易在删除记录或事务回滚之后遇到意外。
普通行号表中的这种整数主键通常是 ROWID 的别名。默认分配算法一般取当前最大行号再加一;AUTOINCREMENT 增加了已提交历史的约束,防止自动分配时复用这个表过去使用过的行号。它处理的是编号复用问题,并不保证每个整数都对应一条现存记录。
删除当前最大值,观察下一次分配
下面使用 Python 标准库里的 sqlite3,在三点十二环境运行。数据库完全位于内存,不会打开已有业务文件。两张表先写入一、二、三,再删除编号三,随后都省略主键插入一次,用同样的操作展示算法差别。
示例还显式控制事务:先完成提交,再尝试插入并回滚,之后重新插入。这样可以区分“曾经在一次事务里短暂出现”与“已经进入提交历史”这两种情况,避免把测试时看到的临时编号误当成永久消耗的号码。
AI概念配图,非真实界面
import sqlite3
db = sqlite3.connect(':memory:', isolation_level=None)
db.execute('CREATE TABLE ordinary(id INTEGER PRIMARY KEY, note TEXT)')
db.execute('CREATE TABLE durable(id INTEGER PRIMARY KEY AUTOINCREMENT, note TEXT)')
for table in ('ordinary', 'durable'):
db.executemany(f'INSERT INTO {table}(note) VALUES (?)',
[('a',), ('b',), ('c',)])
db.execute(f'DELETE FROM {table} WHERE id = 3')
new_id = db.execute(f'INSERT INTO {table}(note) VALUES (?)',
('after-delete',)).lastrowid
print(table + ' next:', new_id)
assert new_id == (3 if table == 'ordinary' else 4)
db.execute('BEGIN')
rolled_id = db.execute("INSERT INTO durable(note) VALUES ('rolled')").lastrowid
db.execute('ROLLBACK')
committed_id = db.execute("INSERT INTO durable(note) VALUES ('kept')").lastrowid
assert rolled_id == committed_id == 5
print('rollback and retry:', rolled_id, committed_id)
rows = db.execute('SELECT id FROM durable ORDER BY id').fetchall()
print('durable rows:', [row[0] for row in rows])
seq = db.execute("SELECT seq FROM sqlite_sequence WHERE name='durable'").fetchone()[0]
print('sequence:', seq)
assert seq == 5
db.close()
print('all rowid checks passed')不复用的承诺有明确范围
普通表再次拿到三,自增表拿到四;后者现有编号因此出现空缺。下一段中,回滚插入拿到五,真正保留的插入也拿到五。这是因为 AUTOINCREMENT 的历史判断只考虑已提交事务,回滚的行号可以被后续自动插入重新使用。
默认算法也不是优先填补所有空洞。如果删掉中间的二,却仍保留最大值三,下一次通常继续分配四,而不是回头寻找最小缺号。本文特意删除最大值,才能稳定复现复用情形;换成任意一条删除后期待同样结果,会得到错误结论。
自动编号还存在整数范围边界。普通算法在最大有符号六十四位整数已经存在时,会尝试寻找其他可用正整数;带 AUTOINCREMENT 的表达到历史上限后,新的自动插入会失败。日常编号很难达到这个上限,但不能因此把规则描述成无限增长。
不要把数据库行号直接当业务流水
sqlite_sequence 是自增算法维护的内部记录。示例只读取它来核对结果,不通过修改它修补断号。手工调整这个表会改变后续分配行为,既不能修复已经对外传播的编号关系,也会让以后排查问题时失去可信的历史线索。
关键字还会带来额外的处理与存储开销。如果业务只需要在当前表里唯一的整数键,普通整数主键通常已经够用;只有确实需要自动分配时避免复用已提交历史编号,才值得选择更强约束。应把需求写进数据模型,而不是因为名字像“自增”就默认添加。
即使选用 AUTOINCREMENT,也不能推出跨表唯一、跨数据库唯一或严格连续。导入时显式给定主键、失败插入以及删除操作都需要单独理解。对外使用的订单号或审核流水如果另有连续性与追踪要求,应设计专门的规则,并与内部主键区分。
迁移前先复现删除最大值、删除中间值、事务回滚和显式编号四类样本,再检查引用这些编号的缓存、导出文件和外部链接。真正要防止的通常是旧编号被误认成新对象,而不是让列表里的数字看起来整齐。


