原文鏈接:http://www.neilconway.org/docs/sequences/
如果不習(xí)慣簡(jiǎn)書(shū)的格式隔嫡,可以點(diǎn)擊下面鏈接查看:筆記版本
在twitter(也可能是其他地方)上很多人在問(wèn)關(guān)于如何在PostgreSql中使用sequence。為了避免重復(fù)回答這個(gè)問(wèn)題,我覺(jué)得在這里總結(jié)一下在Postgresql中使用sequence的基本步驟還是很有意義的募谎。
什么是Sequence?
Sequence是數(shù)據(jù)庫(kù)中一類特殊的對(duì)象士修,其用于生成唯一數(shù)字標(biāo)識(shí)符智什。一個(gè)典型的應(yīng)用場(chǎng)景就是手動(dòng)生成一系列主鍵持灰。Sequence和Mysql中的AUTO_INCREMENT
的概念很像,但又不完全相同怎燥。
在表格中怎么使用Sequence?
Sequence最常見(jiàn)的場(chǎng)景就是生成Serial
這個(gè)偽類型棠涮,Serial
類型主要有下面幾個(gè)特征:
-
Serial
的值就是Sequence生成的。 - 每次調(diào)用Sequence會(huì)生成一個(gè)新值(
Serial
類型的)刺覆。 - 由于Sequence生成的值都是非空的严肪,所以它會(huì)在這一列數(shù)據(jù)上加一個(gè)
NOT NULL
的標(biāo)志。 - Sequence是自動(dòng)生成的谦屑,Postgresql假設(shè)Sequence只用于生成
Series
列(唯一)驳糯,所以如果刪除了這一列,數(shù)據(jù)庫(kù)會(huì)自動(dòng)刪除這個(gè)Sequence氢橙。
例如:下面的命令會(huì)新建一個(gè)表和一個(gè)Sequence對(duì)象酝枢,并且把這個(gè)Sequence對(duì)象關(guān)聯(lián)到這個(gè)表上。
test=# CREATE TABLE users (
test(# id SERIAL, -- assign each user a numeric ID
test(# name TEXT,
test(# age INT4
test(# );
NOTICE: CREATE TABLE will create implicit sequence
"users_id_seq" for serial column "users.id"
CREATE TABLE
在這個(gè)例子里悍手,自動(dòng)生成的Sequence對(duì)象被自動(dòng)命名為user_id_seq
帘睦。如果不想讓數(shù)據(jù)庫(kù)自動(dòng)命名的話袍患,可以調(diào)用下面介紹的pg_get_serial_sequence()
這個(gè)函數(shù)。
需要注意的是使用Series
列并不會(huì)自動(dòng)創(chuàng)建索引竣付,也不會(huì)把這一列標(biāo)注為主鍵诡延。解決這個(gè)問(wèn)題也很簡(jiǎn)單,只需要手動(dòng)加上PRIMARY KEY
這個(gè)標(biāo)示即可古胆。
CREATE TABLE users (
-- make the "id" column a primary key; this also creates
-- a UNIQUE constraint and a b+-tree index on the column
id SERIAL PRIMARY KEY,
name TEXT,
age INT4
);
怎么把Sequence的值賦給新插入的數(shù)據(jù)?
如果你用了Serial
變量肆良,其默認(rèn)值就是Sequence下一次生成的值。為了讓插入的時(shí)候取到這個(gè)默認(rèn)值逸绎,要么忽略插入對(duì)象中Serial
這一列的值惹恃,要么在這個(gè)位置寫(xiě)上DEFAULT
這個(gè)關(guān)鍵字。
下面是例子棺牧,二者完全等價(jià):
-- 忽略id
INSERT INTO users (name, age) VALUES ('Mozart', 20);
-- 使用DEFAULT關(guān)鍵字
INSERT INTO users (name, age, id) VALUES ('Mozart', 20, DEFAULT);
怎么獲取Sequence最新生成的數(shù)據(jù)?
你可以使用currval()
這個(gè)函數(shù)來(lái)獲取Sequence最新生成的值巫糙,需要注意的是這里獲取的是本次session中的值,這么設(shè)計(jì)是有目的的颊乘。currval()
需要一個(gè)參數(shù):Sequence的名字曲秉。可以調(diào)用pg_get_serial_sequence()
來(lái)找到和指定列關(guān)聯(lián)的Sequence的名字疲牵。
下面是例子:
SELECT currval(pg_get_serial_sequence('users', 'id'));
需要注意的是,如果該Sequence在本次session中從來(lái)沒(méi)有生成過(guò)新的數(shù)據(jù)榆鼠,則
currval()
會(huì)報(bào)錯(cuò)纲爸。
會(huì)不會(huì)有競(jìng)爭(zhēng)存在?
假如一個(gè)數(shù)據(jù)庫(kù)客戶端插入了一個(gè)Sequence生成的值,與此同時(shí)另一個(gè)客戶端又插入了一個(gè)值妆够,這樣currval()
獲取的值豈不是有可能取到一個(gè)錯(cuò)誤的值识啦?
事實(shí)上并不會(huì)這樣,Sequence通過(guò)優(yōu)雅的設(shè)計(jì)避免了這個(gè)問(wèn)題神妹。currval()
這回返回當(dāng)前session的新值颓哮,所以其他用戶的插入并不會(huì)改變currval()
返回的值,只有該用戶的插入操作才會(huì)更新該值鸵荠。
插入并獲取插入的數(shù)據(jù)ID豈不是需要兩個(gè)Query語(yǔ)句?
使用上文中所說(shuō)的currval()
我們需要執(zhí)行兩次Query語(yǔ)句:一個(gè)用于插入數(shù)據(jù)冕茅,另一個(gè)用于獲取新插入的ID。為了減少和數(shù)據(jù)庫(kù)連接的次數(shù)蛹找,我們可以把插入和獲取ID的兩條指令連起來(lái)一起丟到數(shù)據(jù)庫(kù)執(zhí)行(譯者注:不熟悉PHP姨伤,不過(guò)個(gè)人不推薦這么做,畢竟不是原子操作庸疾,高并發(fā)長(zhǎng)連接的情況下出錯(cuò)的可能性應(yīng)該還是有的)乍楚。下面是例子(PHP,在nodejs和python里可以有類似的寫(xiě)法):
pg_exec("INSERT INTO users (name, age) VALUES ('Bach', 15);
SELECT currval(pg_get_serial_sequence('users', 'id'));")
這條指令雖有兩條命令届慈,但是只需要和數(shù)據(jù)庫(kù)通信一次即可徒溪,所以基本可以忽略第二條query的額外耗時(shí)忿偷。
另外,對(duì)于Postgresql 8.2以后的版本臊泌,可以直接采用RETURNING
語(yǔ)法解決這個(gè)問(wèn)題鲤桥。(推薦這種方式)
INSERT INTO users (name, age) VALUES ('Liszt', 10) RETURNING id;
該語(yǔ)句會(huì)返回新插入的行的ID。
Sequence生成的數(shù)據(jù)的范圍?
Sequence生成的是64位的整形數(shù)據(jù)缺虐,我們?cè)谏厦嬗玫腟erial類型是一個(gè)32位的整形芜壁,如果需要使用64位的Serial則需要使用Serial8類型。
Sequence生成的數(shù)據(jù)會(huì)不連續(xù)嗎?
當(dāng)然會(huì)高氮,Sequence是用于生成唯一的數(shù)據(jù)標(biāo)識(shí)慧妄,并不需要嚴(yán)格連續(xù)。比如:如果兩個(gè)客戶端同時(shí)插入不同的數(shù)據(jù)(會(huì)調(diào)用nextval()
)的時(shí)候剪芍,每個(gè)客戶端會(huì)得到一個(gè)新的Sequence值塞淹。如果其中一個(gè)在事務(wù)中失敗或者其他原因回滾了,這個(gè)時(shí)候就會(huì)出現(xiàn)Sequence數(shù)據(jù)(即ID)不連續(xù)的現(xiàn)象罪裹,這只是其中一個(gè)例子饱普。
修復(fù)這個(gè)問(wèn)題也很簡(jiǎn)單,具體參考下面的鏈接解決ID不連續(xù)的問(wèn)題
事務(wù)中的Sequence
Sequence操作是基于session的状共,與事務(wù)無(wú)關(guān)套耕。nextval()
會(huì)遞增Sequence的值,但是即便是事務(wù)回滾了Sequence也不會(huì)撤銷峡继,而不論是在事務(wù)內(nèi)外冯袍,currval()
都會(huì)返回Sequence最新的值。
兩個(gè)表共享一個(gè)Sequence?
實(shí)現(xiàn)這個(gè)功能的最簡(jiǎn)單做法就是手動(dòng)生成一個(gè)Sequence碾牌,然后不要使用Serial類型康愤,而是手動(dòng)把Sequence綁定到對(duì)應(yīng)的列。
下面是具體例子:
CREATE SEQUENCE common_fruit_id_seq;
CREATE TABLE apples (
id INT4 DEFAULT nextval('common_fruit_id_seq') NOT NULL,
price NUMERIC
);
CREATE TABLE oranges (
id INT4 DEFAULT nextval('common_fruit_id_seq') NOT NULL,
weight NUMERIC
);
nextval()
會(huì)生成一系列新的值舶吗。需要注意的是這個(gè)手動(dòng)生成的Sequence并不會(huì)隨著表被自動(dòng)刪除征冷,而且你也沒(méi)法調(diào)用 pg_get_serial_sequence()
這個(gè)函數(shù)。
更多Sequence的資料
見(jiàn)Postgresql文檔:
說(shuō)明:本文是基于8.2或者更早的版本進(jìn)行講解的誓琼,翻譯本文的時(shí)候Postgresql已經(jīng)更新到了9.6检激,細(xì)節(jié)可能會(huì)有所不同。