最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

PostgreSQL自增主鍵序列的避坑指南

 更新時間:2026年06月03日 09:12:05   作者:逍遙運(yùn)德  
自增主鍵是數(shù)據(jù)庫表設(shè)計(jì)中的經(jīng)典策略,確保數(shù)據(jù)唯一性和提升寫入性能,本文主要介紹了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ì)理解:

  1. 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)去。

  1. ::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 DEFAULTGENERATED BY DEFAULT AS IDENTITY較靈活。平時自動生成,但也允許手動插入指定的 ID3。適合需要偶爾手動遷移歷史數(shù)據(jù)、或指定特定 ID
ALWAYSGENERATED 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í)際場景選擇:

  1. 查看下一個待生成的值(最常用) 如果想知道下一次調(diào)用 nextval() 會產(chǎn)生什么 ID,可以直接查詢序列對象:
-- 將序列的下一個值重置為 1000
ALTER SEQUENCE t_user_03_id_seq RESTART WITH 1000;

注:last_value 加上步長(increment_by)通常就是下一個生成的值1。

  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)錯。

  1. 查看表中實(shí)際已分配的最大 ID(排查沖突最穩(wěn)妥) 最靠譜查看方式是直接去表里查最大的 ID 是多少。這能判斷序列是否落后于實(shí)際數(shù)據(jù):
SELECT MAX(id) FROM t_user_01;

如果這個值比序列的 last_value 還要大,就說明序列確實(shí)滯后了,需要執(zhí)行之前提到的 setval 語句來同步。

  1. 動態(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)的方法:

  1. 新建表時設(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)
);
  1. 修改現(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ù))時開始生效。

  1. 進(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)文章

  • pgsql批量修改sequences的start方式

    pgsql批量修改sequences的start方式

    這篇文章主要介紹了pgsql批量修改sequences的start方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • PostgreSQL數(shù)據(jù)庫中to_timestamp函數(shù)用法示例

    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 ALTER TABLE 命令常用操作

    PostgreSQL ALTER TABLE 命令常用操作

    本文詳細(xì)介紹了PostgreSQL的ALTER TABLE命令,包括命令格式、常用操作、注意事項(xiàng)及總結(jié),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2026-02-02
  • 在PostgreSQL中設(shè)置表中某列值自增或循環(huán)方式

    在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í)例

    這篇文章主要介紹了PostgreSql 導(dǎo)入導(dǎo)出sql文件格式的表數(shù)據(jù)實(shí)例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-01-01
  • PostgreSQL psql 常用命令總結(jié)

    PostgreSQL psql 常用命令總結(jié)

    psql是PostgreSQL的一個命令行交互式客戶端工具,它具有非常豐富的功能,類似于Oracle的命令行工具sqlplus,本文給大家總結(jié)下PostgreSQL 中常用 psql 常用命令以便后續(xù)查閱,感興趣的朋友跟隨小編一起看看吧
    2023-07-07
  • PostgreSQL中關(guān)閉死鎖進(jìn)程的方法

    PostgreSQL中關(guān)閉死鎖進(jìn)程的方法

    這篇文章主要介紹了PostgreSQL中關(guān)閉死鎖進(jìn)程的方法,本文給出兩種解決這問題的方法,需要的朋友可以參考下
    2015-02-02
  • phpPgAdmin 常見錯誤和問題的解決辦法

    phpPgAdmin 常見錯誤和問題的解決辦法

    這篇文章主要介紹了phpPgAdmin 常見錯誤和問題的解決辦法,如安裝錯誤、登陸錯誤、轉(zhuǎn)儲功能、其它錯誤和問題等,需要的朋友可以參考下
    2014-03-03
  • PostgreSQL如何查看事務(wù)所占有的鎖實(shí)操指南

    PostgreSQL如何查看事務(wù)所占有的鎖實(shí)操指南

    這篇文章主要給大家介紹了關(guān)于PostgreSQL如何查看事務(wù)所占有鎖的相關(guān)資料,文中通過代碼以及圖文介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用PostgreSQL具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-10-10
  • postgresql中時間轉(zhuǎn)換和加減操作

    postgresql中時間轉(zhuǎn)換和加減操作

    這篇文章主要介紹了postgresql中時間轉(zhuǎn)換和加減操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12

最新評論

同仁县| 吉安市| 马龙县| 双流县| 台中市| 姚安县| 陆河县| 中宁县| 长沙市| 阿合奇县| 临颍县| 都匀市| 大渡口区| 广州市| 神农架林区| 旌德县| 杭州市| 中方县| 岗巴县| 静安区| 松阳县| 博客| 万源市| 辽中县| 中牟县| 日喀则市| 平江县| 合水县| 宁武县| 抚顺县| 南城县| 竹溪县| 临城县| 定襄县| 类乌齐县| 珠海市| 报价| 高安市| 林口县| 玉龙| 台北县|