PostgreSQL自增主鍵序列的避坑指南
自增主鍵是數(shù)據(jù)庫表設(shè)計(jì)中非常經(jīng)典且常用的一種主鍵策略。簡單來說,它就是一個能自動遞增的字段,用來作為表中每一行數(shù)據(jù)的唯一身份標(biāo)識(主鍵)。
當(dāng)在表中插入一條新數(shù)據(jù)時,不需要手動去指定這個主鍵的值,數(shù)據(jù)庫系統(tǒng)會自動生成一個唯一的、且比上一條記錄更大的數(shù)值(比如 1, 2, 3...)。
?? 為什么要使用自增主鍵?(核心優(yōu)勢)
1. 保證數(shù)據(jù)的唯一性
自增主鍵能確保每條記錄都有一個獨(dú)一無二的“身份證號”,徹底避免了因?yàn)槭謩臃峙渲麈I可能導(dǎo)致的重復(fù)或沖突問題,保證了數(shù)據(jù)的完整性。
2. 極高的寫入與查詢性能
這是自增主鍵最大的優(yōu)勢。因?yàn)?ID 是按順序遞增的,新插入的數(shù)據(jù)總是排在現(xiàn)有數(shù)據(jù)的末尾。數(shù)據(jù)庫在存儲時(尤其是使用 B+ 樹索引時),不需要頻繁地移動或重排已有的數(shù)據(jù)頁,極大地減少了磁盤碎片,從而讓數(shù)據(jù)的插入和查詢效率都非常高。
3. 簡化開發(fā)邏輯
對于程序員來說,使用自增主鍵非常省心。在寫代碼插入數(shù)據(jù)時,完全不需要去操心“這個 ID 該填多少”,直接交給數(shù)據(jù)庫自動生成即可,大大減少了業(yè)務(wù)代碼的復(fù)雜度。
??? 不同數(shù)據(jù)庫是如何實(shí)現(xiàn)的?
PostgreSQL、MySQL 和 SQL Server 在實(shí)現(xiàn)自增 ID 的底層原理上有著顯著的區(qū)別。簡單來說,它們分別代表了 “獨(dú)立對象模式” 、 “表屬性計(jì)數(shù)器模式” 和 “元數(shù)據(jù)強(qiáng)綁定模式” 。
以下是這三種數(shù)據(jù)庫自增 ID 底層原理的深度解析:
?? 1.PostgreSQL:獨(dú)立的序列(Sequence)對象
在 PostgreSQL 中,自增 ID 并不是直接依附于表的,而是依賴于一個獨(dú)立的數(shù)據(jù)庫對象——序列(Sequence)。
底層實(shí)現(xiàn):
當(dāng)創(chuàng)建一個 SERIAL 或 IDENTITY 類型的自增列時,PostgreSQL 會在后臺自動創(chuàng)建一個名為 表名_字段名_seq 的獨(dú)立序列對象。該字段的默認(rèn)值被設(shè)置為 nextval('序列名')。
核心特性:
獨(dú)立性:序列獨(dú)立于表存在,甚至可以跨表共享同一個序列。
事務(wù)非原子性(不回滾):序列值的分配不跟隨事務(wù)回滾。如果一個事務(wù)獲取了 ID=100 但隨后回滾了,這個 100 就會被“浪費(fèi)”掉,下一個事務(wù)會直接獲取 101。這種設(shè)計(jì)是為了在高并發(fā)環(huán)境下,避免為了等待事務(wù)提交而加鎖,從而極大地提升了并發(fā)插入的性能。
??2. MySQL:表級別的自增計(jì)數(shù)器
在 MySQL(以主流的 InnoDB 存儲引擎為例)中,自增 ID 是作為表的一個屬性來維護(hù)的。
底層實(shí)現(xiàn):
InnoDB 在內(nèi)存中為每個帶有自增主鍵的表維護(hù)一個自增計(jì)數(shù)器(Auto-increment Counter)。當(dāng)需要插入新數(shù)據(jù)時,MySQL 會根據(jù)配置的鎖模式(innodb_autoinc_lock_mode)獲取鎖,讀取當(dāng)前計(jì)數(shù)器的值作為新 ID,然后將計(jì)數(shù)器加 。
核心特性:
依附于表:自增邏輯完全綁定在具體的表上,無法像 PostgreSQL 的序列那樣跨表共享。 預(yù)分配與空洞:為了保證高并發(fā)插入的性能,MySQL 在批量插入(如 INSERT ... SELECT)時會預(yù)分配一段 ID 區(qū)間。如果批量插入中途失敗或事務(wù)回滾,這些預(yù)分配的 ID 就會被直接丟棄,導(dǎo)致 ID 出現(xiàn)較大的跳躍。
重啟恢復(fù)機(jī)制的進(jìn)化: MySQL 5.7 及更早版本重啟后需掃描全表最大值來恢復(fù)計(jì)數(shù)器;而 MySQL 8.0 及更高版本將計(jì)數(shù)器的變更記錄在 Redo Log 中,重啟時直接從日志恢復(fù),不再需要掃描全表。
??? 3.SQL Server:基于元數(shù)據(jù)的 IDENTITY 屬性
SQL Server 的 IDENTITY 屬性在底層設(shè)計(jì)上有著自己獨(dú)特的“強(qiáng)綁定”哲學(xué)。
底層實(shí)現(xiàn):
IDENTITY 屬于列定義的元數(shù)據(jù)(metadata-level attribute)。 自增值由存儲引擎在插入時原子生成并寫入頁結(jié)構(gòu),其序列狀態(tài)(如 last_value 等)持久化在系統(tǒng)目錄 sys.identity_columns 中。從 SQL Server 2012 開始,其后臺實(shí)際上也使用了 sequence object(序列對象)進(jìn)行封裝,但它與列定義的綁定非常緊密。
核心特性:
物理存儲耦合性:
由于與表的物理存儲和元數(shù)據(jù)強(qiáng)綁定,SQL Server 不支持通過簡單的 ALTER COLUMN 語句直接為一個已有的普通列添加 IDENTITY 屬性。如果需要這樣做,通常必須重建表(創(chuàng)建新表帶 IDENTITY -> 遷移數(shù)據(jù) -> 刪除舊表)。
事務(wù)日志不可逆性: 若允許隨意修改 IDENTITY,需回滾已插入但未提交的 identity 值,而當(dāng)前的日志格式不記錄 identity 生成上下文,導(dǎo)致無法安全回退。
同樣存在空洞: 與 MySQL 和 PG 一樣,由于刪除記錄、事務(wù)回滾或服務(wù)器重啟等原因,IDENTITY 值也會出現(xiàn)不連續(xù)的情況。
?? 4.三大數(shù)據(jù)庫自增 ID 核心差異總結(jié)
| 特性維度 | PostgreSQL (序列對象) | MySQL (InnoDB 計(jì)數(shù)器) | SQL Server (IDENTITY) |
|---|---|---|---|
| 底層載體 | 獨(dú)立的數(shù)據(jù)庫對象 (Sequence) | 表屬性 (內(nèi)存計(jì)數(shù)器 + 磁盤元數(shù)據(jù)) | 列元數(shù)據(jù) + 系統(tǒng)目錄 (后臺封裝序列) |
| 跨表共享 | 支持 (多個表可共用一個序列) | 不支持 (完全依賴表) | 不支持 (與列強(qiáng)綁定) |
| 事務(wù)回滾 | ID 不回滾,會產(chǎn)生空洞 | ID 不回滾,預(yù)分配也會導(dǎo)致空洞 | ID 不回滾,會產(chǎn)生空洞 |
| 修改靈活性 | 極高 (可獨(dú)立操作序列對象) | 較高 (可通過 ALTER TABLE 修改) | 極低 (不支持直接 ALTER 添加 IDENTITY) |
| 重啟恢復(fù) | 序列值持久化,重啟后繼續(xù)遞增 | 8.0+ 從日志恢復(fù);5.7 需掃描表最大值 | 從系統(tǒng)元數(shù)據(jù)中恢復(fù) |
總結(jié)來說:
PostgreSQL 的自增 ID 最為靈活且獨(dú)立;MySQL 的自增 ID 與表深度綁定,通過預(yù)分配機(jī)制換取高并發(fā)性能;而 SQL Server 的 IDENTITY 則是最為嚴(yán)格和封閉的,將其作為表物理結(jié)構(gòu)的一部分,保證了極高的數(shù)據(jù)一致性但犧牲了修改的靈活性。
?? 需要注意的小特點(diǎn)
ID 不連續(xù)(空洞): 自增主鍵并不保證絕對連續(xù)。如果刪除了某條數(shù)據(jù),或者插入數(shù)據(jù)的事務(wù)發(fā)生了回滾,已經(jīng)分配出去的 ID 通常不會被回收,從而產(chǎn)生“空洞”。
適用場景: 它非常適合絕大多數(shù)業(yè)務(wù)場景。但在某些特殊需求下(例如需要跨數(shù)據(jù)庫合并數(shù)據(jù)、或者主鍵本身需要有特定的業(yè)務(wù)含義時),可能會選擇 UUID 或其他形式的主鍵。
?? PostgreSQL 建表時如何創(chuàng)建自增主鍵
方法1
CREATE TABLE public.t_user_01 (
id int4 DEFAULT nextval('users1_id_seq'::regclass) NOT NULL,
user_name varchar(100) NOT NULL,
-- 其他字段...
CONSTRAINT users1_pkey PRIMARY KEY (id)
);
在這段建表語句中,nextval('users1_id_seq'::regclass) 是一個數(shù)據(jù)庫函數(shù)調(diào)用,它的核心作用是為 id 字段生成一個唯一的、自增的序列值,通常用來作為表的主鍵。
可以把它拆解為三個部分來詳細(xì)理解:
- nextval 函數(shù) nextval(即 "next value" 的縮寫)是數(shù)據(jù)庫(如 PostgreSQL、openGauss 等)中用于操作“序列(Sequence)”的核心函數(shù)。
作用:每次調(diào)用它時,它都會讓指定的序列對象遞增(比如從 1 變成 2),并返回這個新的數(shù)值。 特性:它能保證在多用戶并發(fā)訪問時,每個進(jìn)程都能安全地獲取到一個不重復(fù)的唯一值。 2. 'users1_id_seq' 序列名 這是 nextval 函數(shù)要操作的具體目標(biāo),即一個名為 users1_id_seq 的序列對象1。
序列是一個獨(dú)立的數(shù)據(jù)庫對象,專門用來按既定規(guī)則(如每次加1)生成數(shù)字。 當(dāng)在 id 字段設(shè)置這個默認(rèn)值后,每次向 t_user_01 表插入一條新數(shù)據(jù)且沒有手動指定 id 時,數(shù)據(jù)庫就會自動從 users1_id_seq 這個序列里“拿”下一個數(shù)字填進(jìn)去。
- ::regclass 類型轉(zhuǎn)換 這是 PostgreSQL 等數(shù)據(jù)庫特有的語法,表示將前面的字符串 'users1_id_seq' 強(qiáng)制轉(zhuǎn)換為 regclass 數(shù)據(jù)類型。
為什么要轉(zhuǎn)換? nextval 函數(shù)的參數(shù)要求是 regclass 類型。regclass 是數(shù)據(jù)庫內(nèi)部用來存儲對象(如表、序列)OID(對象標(biāo)識符)的一種特殊數(shù)據(jù)類型。
轉(zhuǎn)換的好處:通過 ::regclass 轉(zhuǎn)換,數(shù)據(jù)庫會在編譯或準(zhǔn)備階段就鎖定這個序列的真實(shí)身份(即“早期綁定”)。這意味著,即使以后給這個序列改了名或者移動了模式(schema),只要 OID 沒變,這個默認(rèn)值表達(dá)式依然能準(zhǔn)確找到它,避免了運(yùn)行時查找可能出現(xiàn)的錯誤。
?? 補(bǔ)充說明:
這種寫法(int4 DEFAULT nextval('...'::regclass))是早期 PostgreSQL 版本中定義自增主鍵的常見方式。
在現(xiàn)代的 PostgreSQL 開發(fā)中,為了書寫簡便,通常會直接使用 SERIAL 或 BIGSERIAL 類型,或者使用標(biāo)準(zhǔn)的 GENERATED BY DEFAULT AS IDENTITY 語法,它們在底層其實(shí)也是自動創(chuàng)建了序列并綁定了類似的 nextval 邏輯。
方法2
CREATE TABLE t_user_023 (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_name varchar(100) NOT NULL
-- 其他字段...
);
-- 或者
CREATE TABLE public.t_user_03 (
id int4 GENERATED BY DEFAULT AS IDENTITY( INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START 1 CACHE 1 NO CYCLE) NOT NULL,
user_name varchar(100) NOT NULL,
-- 其他字段...
CONSTRAINT t_user_023_pkey PRIMARY KEY (id)
);
核心字段 定義
id INT: 定義了一個名為 id 的字段,數(shù)據(jù)類型為 INT(4字節(jié)整數(shù))。
GENERATED BY DEFAULT AS IDENTITY:
這是整段語句的精髓,也是 PostgreSQL 10 及以上版本推薦的自增主鍵寫法(符合 SQL 標(biāo)準(zhǔn))。
GENERATED ... AS IDENTITY: 表示這個字段的值由數(shù)據(jù)庫自動生成,底層會自動創(chuàng)建一個獨(dú)立的序列(Sequence)對象來管理數(shù)字的遞增2。 BY DEFAULT:這是它的行為模式。意思是“默認(rèn)情況下自動生成,但也允許手動指定”。 如果插入數(shù)據(jù)時不寫 id,數(shù)據(jù)庫會自動從序列中拿下一個數(shù)字填進(jìn)去。 如果在插入數(shù)據(jù)時強(qiáng)行指定了 id(例如 INSERT INTO t_user_023 (id, user_name) VALUES (100, '張三')),數(shù)據(jù)庫也會接受,而不會報(bào)錯。
PRIMARY KEY:將 id 字段設(shè)為主鍵。 這意味著 id 的值必須是唯一的,且不能為空。數(shù)據(jù)庫會自動為該字段創(chuàng)建一個唯一索引,以加快查詢速度。
?? 深度對比:BY DEFAULT 與 ALWAYS
在定義自增列時,除了 BY DEFAULT,還有一個常用的模式是 GENERATED ALWAYS AS IDENTITY。兩者的區(qū)別決定了平時插數(shù)據(jù)時的“自由度”:
| 模式 | 語法 | 行為特點(diǎn) | 適用場景 |
|---|---|---|---|
| BY DEFAULT | GENERATED BY DEFAULT AS IDENTITY | 較靈活。平時自動生成,但也允許手動插入指定的 ID3。 | 適合需要偶爾手動遷移歷史數(shù)據(jù)、或指定特定 ID |
| ALWAYS | GENERATED ALWAYS AS IDENTITY | 較嚴(yán)格。永遠(yuǎn)由數(shù)據(jù)庫自動生成。如果嘗試手動插入 ID,數(shù)據(jù)庫會直接報(bào)錯拒絕(除非使用特殊的 OVERRIDING SYSTEM VALUE 語法)1。 | 適合絕大多數(shù)常規(guī)業(yè)務(wù),能最大程度保證主鍵的純凈,防止人為插錯數(shù)據(jù)。 |
總結(jié)來說,提供的這段建表語句,創(chuàng)建了一個帶有 “靈活自增主鍵” 的用戶表。它既享受了數(shù)據(jù)庫自動分配唯一 ID 的便利,又保留了在特殊情況下(如數(shù)據(jù)遷移)手動控制 ID 的權(quán)利。
?? PostgreSQL 怎操作序列值?
在 PostgreSQL 中,查看序列的“當(dāng)前值”其實(shí)有兩種不同的需求:一種是查看下一個待生成的值,另一種是查看目前表中實(shí)際已分配的最大值。
這里有 4 種常用的方法,可以根據(jù)實(shí)際場景選擇:
- 查看下一個待生成的值(最常用) 如果想知道下一次調(diào)用 nextval() 會產(chǎn)生什么 ID,可以直接查詢序列對象:
-- 將序列的下一個值重置為 1000 ALTER SEQUENCE t_user_03_id_seq RESTART WITH 1000;
注:last_value 加上步長(increment_by)通常就是下一個生成的值1。
- 查看當(dāng)前會話最后一次獲取的值 如果在當(dāng)前會話(Session)中剛剛調(diào)用過 nextval('users1_id_seq'),可以用這個函數(shù)查看:
SELECT currval('users1_id_seq');
?? 注意:currval 有嚴(yán)格的限制。它只能查看當(dāng)前會話中最近一次獲取的值。如果剛連上數(shù)據(jù)庫還沒用過這個序列,執(zhí)行這條會直接報(bào)錯。
- 查看表中實(shí)際已分配的最大 ID(排查沖突最穩(wěn)妥) 最靠譜查看方式是直接去表里查最大的 ID 是多少。這能判斷序列是否落后于實(shí)際數(shù)據(jù):
SELECT MAX(id) FROM t_user_01;
如果這個值比序列的 last_value 還要大,就說明序列確實(shí)滯后了,需要執(zhí)行之前提到的 setval 語句來同步。
- 動態(tài)獲取序列的最新已分配值(生產(chǎn)級通用寫法) 如果不確定序列的確切名字,或者想要一個更健壯、能直接反映“最新已分配值”的查詢,可以使用 PostgreSQL 的內(nèi)置函數(shù)組合:
-- 自動根據(jù)表和字段名找到關(guān)聯(lián)的序列,并獲取其最新已分配值
SELECT pg_sequence_last_value(pg_get_serial_sequence('t_user_01', 'id')::regclass);
pg_get_serial_sequence('表名', '字段名'):能自動查出 t_user_01 表的 id 字段綁定的是哪個序列(防止序列名記錯)。
pg_sequence_last_value:基于數(shù)據(jù)庫日志解析,能非常準(zhǔn)確地返回序列的真實(shí)進(jìn)度3。
?? 總結(jié)建議:
日常開發(fā)想看下一個 ID 是多少,用 方法 1 (SELECT * FROM 序列名)。
排查主鍵沖突或做數(shù)據(jù)遷移校驗(yàn)時,用 方法 3 (SELECT MAX(id)) 結(jié)合 方法 4 最為準(zhǔn)確可
5.修改自增主鍵的起始值
在 PostgreSQL 中,修改自增主鍵的起始值,本質(zhì)上就是修改它背后綁定的序列。
這里有三種最常用且實(shí)用的方法:
方法一:直接指定一個固定的起始值 如果下一個插入 ID 從一個特定的數(shù)字(例如 1000)開始,可以使用 ALTER SEQUENCE 命令。
首先,需要知道 id 字段綁定的序列名(通常默認(rèn)為 表名_字段名_seq即 t_user_03_id_seq)。然后執(zhí)行:
-- 將序列的下一個值重置為 1000 ALTER SEQUENCE t_user_03_id_seq RESTART WITH 1000;
執(zhí)行后,下一次插入數(shù)據(jù)時,id 就會從 1000 開始1。
方法二:同步到當(dāng)前表的最大 ID(最推薦) 如果因?yàn)槭謩硬迦肓艘恍?shù)據(jù),或者刪除了部分?jǐn)?shù)據(jù),想要讓自增 ID 接上當(dāng)前表里實(shí)際最大的 ID,避免主鍵沖突,這是最穩(wěn)妥的方法。可以結(jié)合 setval 函數(shù)和子查詢,直接把序列的值撥到當(dāng)前表的最大 ID 處:
-- 將序列的當(dāng)前值設(shè)置為表中最大的 id
SELECT setval('t_user_03_id_seq', (SELECT COALESCE(MAX(id), 0) FROM t_user_023));
MAX(id) 會查出當(dāng)前表里最大的 ID。
COALESCE(..., 0) 是為了防止表是空的(MAX 結(jié)果為 NULL)導(dǎo)致報(bào)錯,空表時默認(rèn)從 0 開始,下次自增就是 18。
setval 設(shè)置后,序列的下一個值會自動變成 最大值 + 12。
方法三:全自動獲取序列名并修改(最安全)
如果不確定序列的具體名稱,可以使用 PostgreSQL 自帶的 pg_get_serial_sequence 函數(shù)來自動獲取。這在生產(chǎn)環(huán)境中非常實(shí)用,能有效避免因序列名寫錯導(dǎo)致的報(bào)錯。這條語句完全不需要輸入序列名,直接復(fù)制粘貼表名即可使用。
-- 自動獲取 t_user_02 表 id 字段綁定的序列,并將其重置為當(dāng)前最大 ID
SELECT setval(
pg_get_serial_sequence('t_user_03', 'id'),
(SELECT COALESCE(MAX(id), 0) FROM t_user_03)
);
?? PostgreSQL 自增主鍵的步進(jìn)設(shè)置
在 PostgreSQL 中,自增主鍵的步進(jìn)(即每次增加的數(shù)值,默認(rèn)為 1)是通過底層的序列(Sequence)對象來控制的。
根據(jù)新建表還是修改現(xiàn)有表,有以下幾種設(shè)置步進(jìn)的方法:
新建表時設(shè)置步進(jìn)
如果正在創(chuàng)建一張新表,可以通過以下兩種方式自定義步進(jìn):
方式一:使用標(biāo)準(zhǔn)的 IDENTITY 語法(推薦) 在定義字段時,直接在 GENERATED ... AS IDENTITY 的括號內(nèi)指定 INCREMENT BY:這樣,id 字段每次自增的步長就是 10。
CREATE TABLE orders (
id INT GENERATED BY DEFAULT AS IDENTITY (INCREMENT BY 10) PRIMARY KEY,
order_name VARCHAR(100)
);
方式二:手動創(chuàng)建序列并綁定 先創(chuàng)建一個自定義步進(jìn)的序列,再將其綁定到表字段上:
-- 1. 創(chuàng)建一個步進(jìn)為 5 的序列
CREATE SEQUENCE custom_step_seq INCREMENT BY 5;
-- 2. 建表并綁定該序列
CREATE TABLE products (
id INT PRIMARY KEY DEFAULT nextval('custom_step_seq'),
product_name VARCHAR(100)
);
- 修改現(xiàn)有表的步進(jìn) 如果表已經(jīng)存在,需要先找到它綁定的序列,然后通過 ALTER SEQUENCE 命令來修改步進(jìn)。
步驟一:查找序列名 如果不記得序列的名字,可以使用系統(tǒng)函數(shù)自動獲?。?/p>
-- 將 '表名', '字段名' 替換為的實(shí)際名稱
SELECT pg_get_serial_sequence('表名', 'id');
步驟二:修改步進(jìn) 獲取到序列名(例如 表名_id_seq)后,執(zhí)行以下命令修改步進(jìn)(例如改為每次增加 10):
ALTER SEQUENCE 表名_id_seq INCREMENT BY 10;
注意:修改后,新的步進(jìn)規(guī)則會從下一次調(diào)用 nextval(即下一次插入數(shù)據(jù))時開始生效。
- 進(jìn)階:設(shè)置負(fù)數(shù)步進(jìn)(遞減序列) PostgreSQL 的序列同樣支持負(fù)數(shù)步進(jìn),實(shí)現(xiàn)主鍵的自動遞減:
-- 創(chuàng)建一個從 1000 開始,每次減少 5 的序列 CREATE SEQUENCE countdown_seq START WITH 1000 INCREMENT BY -5 MINVALUE 1; -- 建議設(shè)置最小值,防止無限遞減
?? 性能優(yōu)化與注意事項(xiàng)
避免索引熱點(diǎn):在高并發(fā)寫入的場景下,如果步進(jìn)為 1,新插入的數(shù)據(jù)會集中在 B+ 樹索引的同一頁(頁尾),容易引發(fā)“索引熱點(diǎn)”競爭。將步進(jìn)設(shè)置為較大的數(shù)值(如 10、100),可以讓新生成的 ID 在索引中分布得更分散,從而減少頁競爭,提升寫入性能。
業(yè)務(wù)邏輯風(fēng)險(xiǎn):設(shè)置非 1 的步進(jìn)(尤其是大跨度步進(jìn))會導(dǎo)致主鍵 ID 出現(xiàn)大量“空洞”(不連續(xù))。請確保業(yè)務(wù)邏輯沒有依賴 ID 連續(xù)性的判斷(例如“偶數(shù) ID 代表測試數(shù)據(jù)”等),以免造成業(yè)務(wù)漏洞。
?? PostgreSQL 中非常經(jīng)典的“坑”
問題現(xiàn)象:使用自增ID插入一條數(shù)據(jù),與存在的使用指定ID插入的數(shù)據(jù) ,出現(xiàn)主鍵沖突。
?? 為什么會出現(xiàn)沖突?
根本原因在于:PostgreSQL 的序列(Sequence)和表里的實(shí)際數(shù)據(jù)是相互獨(dú)立的。
當(dāng)手動插入一條指定 ID 的數(shù)據(jù)(例如 INSERT INTO t_user_01 (id, ...) VALUES (100, ...))時,數(shù)據(jù)庫只會乖乖地把這條數(shù)據(jù)存入表中,但它不會自動去更新 users1_id_seq 這個序列的當(dāng)前值。
序列(Sequence)對此一無所知,依然按自己的節(jié)奏往下走。當(dāng)它走到 100 并準(zhǔn)備生成下一個 ID 時,就會撞上剛剛手動插入的 ID 100,從而拋出“主鍵沖突(duplicate key value violates unique constraint)”的錯誤。
??? 如何解決?(重置序列)
解決這個問題的核心思路是:手動把序列的當(dāng)前值,同步到表中最大 ID 的下一個位置。 在數(shù)據(jù)庫中執(zhí)行以下 SQL 語句來修復(fù):
-- 將序列 users1_id_seq 的下一個值重置為表中當(dāng)前最大 id + 1
SELECT setval('users1_id_seq',
(SELECT COALESCE(MAX(id), 0) FROM t_user_01) + 1);
語句原理解析:
(SELECT MAX(id) FROM t_user_01):先查出表里目前最大的那個 ID (假設(shè)是 100)。 序列+1:讓序列從 101 開始,這樣下次插入就不會沖突了。
COALESCE(..., 0):這是一個防御性寫法。萬一表是空的(MAX(id) 為 NULL),它會默認(rèn)返回 0,加 1 后序列從 1 開始,避免報(bào)錯。
setval('序列名', 值):把序列的當(dāng)前值強(qiáng)行設(shè)置為這個計(jì)算出來的新值。 執(zhí)行完這條語句后,再嘗試正常插入數(shù)據(jù)(不指定 ID),nextval() 就會生成一個全新的、不沖突的 ID 了。
??? 以后如何避免?
數(shù)據(jù)遷移/批量導(dǎo)入后必做:
如果使用 COPY 命令或者第三方工具批量導(dǎo)入了帶 ID 的歷史數(shù)據(jù),導(dǎo)入完成后,務(wù)必順手執(zhí)行一次上面的 setval 語句來同步序列。
盡量少手動指定 ID:
在正常的業(yè)務(wù)代碼中,插入數(shù)據(jù)時不要給 id 字段傳值,完全交給數(shù)據(jù)庫自動生成。
建立檢查機(jī)制:
如果是生產(chǎn)環(huán)境,可以定期寫個簡單的腳本,檢查核心表的 MAX(id) 和序列的 last_value 是否一致,及時發(fā)現(xiàn)并修復(fù)。
??? 怎樣讓postgresql自動更新自增id
PostgreSQL 的自增 ID(序列)默認(rèn)情況下不會自動同步表中手動插入的數(shù)據(jù),這正是之前遇到主鍵沖突的原因。不過,可以通過以“自動化”的方式來避免手動執(zhí)行 setval 命令:
??. 使用觸發(fā)器(Trigger)實(shí)現(xiàn)真正的“自動更新” 如果希望每次插入數(shù)據(jù)時,數(shù)據(jù)庫都能自動檢查并修正 ID,可以創(chuàng)建一個觸發(fā)器。這個觸發(fā)器會在每次插入前,自動把序列的值更新為當(dāng)前表中最大 ID 的下一個值。
-- 1. 創(chuàng)建一個自動同步序列的函數(shù)
-- 1. 創(chuàng)建一個自動同步序列的函數(shù)
CREATE OR REPLACE FUNCTION sync_id_sequence()
RETURNS TRIGGER AS $$
BEGIN
-- 將序列 users1_id_seq 的下一個值設(shè)為當(dāng)前表最大ID + 1
PERFORM setval('users1_id_seq', (SELECT COALESCE(MAX(id), 0) FROM t_user_01) + 1);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 2. 為 t_user_01 表創(chuàng)建觸發(fā)器,在每次插入前執(zhí)行該函數(shù)
CREATE TRIGGER before_insert_sync_seq
BEFORE INSERT ON t_user_01
FOR EACH ROW
EXECUTE FUNCTION sync_id_sequence();
?? 提示:這種方法雖然實(shí)現(xiàn)了全自動,但因?yàn)槊看尾迦攵家~外執(zhí)行一次 MAX(id) 查詢,在數(shù)據(jù)量極大的高并發(fā)場景下可能會輕微影響寫入性能。
到此這篇關(guān)于PostgreSQL自增主鍵序列的避坑指南的文章就介紹到這了,更多相關(guān)PostgreSQL自增主鍵序列內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
PostgreSQL數(shù)據(jù)庫中to_timestamp函數(shù)用法示例
PostgreSQL 的 to_timestamp 函數(shù)可以將字符串或整數(shù)轉(zhuǎn)換為時間戳,這篇文章主要介紹了PostgreSQL數(shù)據(jù)庫中to_timestamp函數(shù)用法的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-08-08
在PostgreSQL中設(shè)置表中某列值自增或循環(huán)方式
這篇文章主要介紹了在PostgreSQL中設(shè)置表中某列值自增或循環(huán)方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01
PostgreSql 導(dǎo)入導(dǎo)出sql文件格式的表數(shù)據(jù)實(shí)例
這篇文章主要介紹了PostgreSql 導(dǎo)入導(dǎo)出sql文件格式的表數(shù)據(jù)實(shí)例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01
PostgreSQL中關(guān)閉死鎖進(jìn)程的方法
這篇文章主要介紹了PostgreSQL中關(guān)閉死鎖進(jìn)程的方法,本文給出兩種解決這問題的方法,需要的朋友可以參考下2015-02-02
PostgreSQL如何查看事務(wù)所占有的鎖實(shí)操指南
這篇文章主要給大家介紹了關(guān)于PostgreSQL如何查看事務(wù)所占有鎖的相關(guān)資料,文中通過代碼以及圖文介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用PostgreSQL具有一定的參考借鑒價值,需要的朋友可以參考下2023-10-10

