詳解數倉對象設計中序列SEQUENCE原理與應用

本文分享自華爲雲社區《GaussDB(DWS)對象設計之序列SEQUENCE原理與使用方法介紹》,作者:VV一笑。

1. 前言

  • 適用版本:8.2.1及以上版本

序列SEQUENCE用來生成唯一整數的數據庫對象,本文對序列SEQUENCE的使用場景、使用方法及相關函數進行了介紹,並針對序列SEQUENCE在使用中容易遇到的問題和對應的解決方法進行了梳理總結。

2. SEQUENCE——自增整數序列

序列Sequence是用來產生唯一整數的數據庫對象。序列的值是按照一定規則自增的整數。因爲自增所以不重複,因此說Sequence具有唯一標識性。因此,在數據庫中Sequence常常被作爲主鍵使用。

3. 創建序列

通過序列使某字段成爲唯一標識符的方法有兩種:

  • 是聲明字段的類型爲序列整型,由數據庫在後臺自動創建一個對應的Sequence。
  • 使用CREATE SEQUENCE自定義一個新的Sequence,然後將nextval(‘sequence_name’)函數讀取的序列值,指定爲某一字段的默認值,這樣該字段就可以作爲唯一標識符。

方法一: 聲明字段類型爲序列整型來定義標識符字段。例如:

postgres=# CREATE TABLE T1
(
    id    serial,
    name  text
);

方法二: 創建序列,並通過nextval(‘sequence_name’)函數指定爲某一字段的默認值。這種方式更靈活,可以爲序列定義cache,一次預申請多個序列值,減少與GTM的交互次數,來提高性能。
1.創建序列

postgres=# CREATE SEQUENCE seq1 cache 100;

2.指定爲某一字段的默認值,使該字段具有唯一標識屬性。

postgres=# CREATE TABLE T2 
( 
    id   int not null default nextval('seq1'),
    name text
);

【注意】

除了爲序列指定了cache,方法二所實現的功能基本與方法一類似。但是一旦定義cache,序列將會產生空洞(序列值爲不連貫的數值,如:1.4.5),並且不能保序。另外爲某序列指定從屬列後,該列刪除,對應的sequence也會被刪除。 雖然數據庫並不限制序列只能爲一列產生默認值,但最好不要多列共用同一個序列。
當前版本只支持在定義表的時候指定自增列,或者指定某列的默認值爲nextval(‘seqname’), 不支持在已有表中增加自增列或者增加默認值爲nextval(‘seqname’)的列。

3.1 CREATE SEQUENCE語句的使用方法

CREATE SEQUENCE用於向當前數據庫裏增加一個新的序列。序列的Owner爲創建此序列的用戶。

注意事項

  • Sequence是一個存放等差數列的特殊表,該表受DBMS控制。這個表沒有實際意義,通常用於爲行或者表生成唯一的標識符。
  • 如果給出一個模式名,則該序列就在給定的模式中創建,否則會在當前模式中創建。序列名必須和同一個模式中的其他序列、表、索引、視圖或外表的名字不同。
  • 創建序列後,在表中使用序列的nextval()函數和generate_series(1,N)函數對錶插入數據,請保證nextval的可調用次數大於等於N+1次,否則會因爲generate_series()函數會調用N+1次而導致報錯。
  • 不支持在template1數據庫中創建SEQUENCE。

語法格式

CREATE SEQUENCE name [ INCREMENT [ BY ] increment ]
    [ MINVALUE minvalue | NO MINVALUE | NOMINVALUE ] [ MAXVALUE maxvalue | NO MAXVALUE | NOMAXVALUE] 
    [ START [ WITH ] start ] [ CACHE cache ] [ [ NO ] CYCLE | NOCYCLE ] 
    [ OWNED BY { table_name.column_name | NONE } ];

參數說明

  • name

將要創建的序列名稱。

取值範圍: 僅可以使用小寫字母(a~z)、 大寫字母(A~Z),數字和特殊字符"#","_","$"的組合。

  • increment

指定序列的步長。一個正數將生成一個遞增的序列,一個負數將生成一個遞減的序列。

缺省值爲1。

  • MINVALUE minvalue | NO MINVALUE| NOMINVALUE

執行序列的最小值。如果沒有聲明minvalue或者聲明瞭NO MINVALUE,則遞增序列的缺省值爲1,遞減序列的缺省值爲-263-1。NOMINVALUE等價於NO MINVALUE

  • MAXVALUE maxvalue | NO MAXVALUE| NOMAXVALUE

執行序列的最大值。如果沒有聲明maxvalue或者聲明瞭NO MAXVALUE,則遞增序列的缺省值爲263-1,遞減序列的缺省值爲-1。NOMAXVALUE等價於NO MAXVALUE

start

指定序列的起始值。缺省值:對於遞增序列爲minvalue,遞減序列爲maxvalue。

cache

爲了快速訪問,而在內存中預先存儲序列號的個數。一個緩存週期內,CN不再向GTM索取序列號,而是使用本地預先申請的序列號。

缺省值爲1,表示一次只能生成一個值,也就是沒有緩存。

【注意】

◾不建議同時定義cache和maxvalue或minvalue。因爲定義cache後不能保證序列的連續性,可能會產生空洞,造成序列號段浪費。

◾建議cache值不要設置過大,否則會出現緩存序列號時(每個cache週期的第一個nextval)耗時過長的情況;同時建議cache值小於100000000。實際使用時應根據業務設置合理的cache值,既能保證快速訪問,又不會浪費序列號。

CYCLE

用於使序列達到maxvalue或者minvalue後可循環並繼續下去。

如果聲明瞭NO CYCLE,則在序列達到其最大值後任何對nextval的調用都會返回一個錯誤。

NOCYCLE的作用等價於NO CYCLE。

缺省值爲NO CYCLE。

若定義序列爲CYCLE,則不能保證序列的唯一性。

OWNED BY-

將序列和一個表的指定字段進行關聯。這樣,在刪除那個字段或其所在表的時候會自動刪除已關聯的序列。關聯的表和序列的所有者必須是同一個用戶,並且在同一個模式中。需要注意的是,通過指定OWNED BY,僅僅是建立了表的對應列和sequence之間關聯關係,並不會在插入數據時在該列上產生自增序列。

缺省值爲OWNED BY NONE,表示不存在這樣的關聯。

【注意】

◾通過OWNED BY創建的Sequence不建議用於其他表,如果希望多個表共享Sequence,該Sequence不應該從屬於特定表。

示例

創建一個從101開始的遞增序列,名爲serial:
CREATE SEQUENCE serial
 START 101
 CACHE 20;
從序列中選出下一個數字:
SELECT nextval('serial');
  nextval 
 ---------
    101
從序列中選出下一個自增數字:
SELECT nextval('serial');
  nextval 
 ---------
    102
創建與表關聯的序列:
CREATE TABLE customer_address
(
    ca_address_sk             integer               not null,
    ca_address_id             char(16)              not null,
    ca_street_number          char(10)                      ,
    ca_street_name            varchar(60)                   ,
    ca_street_type            char(15)                      ,
    ca_suite_number           char(10)                      ,
    ca_city                   varchar(60)                   ,
    ca_county                 varchar(30)                   ,
    ca_state                  char(2)                       ,
    ca_zip                    char(10)                      ,
    ca_country                varchar(20)                   ,
    ca_gmt_offset             decimal(5,2)                  ,
    ca_location_type          char(20)                     
) ;
 
CREATE SEQUENCE serial1
 START 101
 CACHE 20
OWNED BY customer_address.ca_address_sk;
使用serial創建主鍵自增序列表serial_table:
CREATE TABLE serial_table(a int, b serial);
INSERT INTO serial_table (a) VALUES (1),(2),(3);
SELECT * FROM serial_table ORDER BY b;
 a | b
---+---
 1 | 1
 2 | 2
 3 | 3
(3 rows)

4. 修改序列

ALTER SEQUENCE命令更改現有序列的屬性,包括修改修改擁有者、歸屬列和最大值。

指定序列與列的歸屬關係:將序列和一個表的指定字段進行關聯。在刪除那個字段或其所在表的時候會自動刪除已關聯的序列。
postgres=# ALTER SEQUENCE seq1 OWNED BY T2.id;
將序列serial的最大值修改爲300:
ALTER SEQUENCE seq1 MAXVALUE 300;

4.1 ALTER SEQUENCE語句的使用方法

ALTER SEQUENCE用於修改一個現有的序列的參數。

注意事項

  • 使用ALTER SEQUENCE的用戶必須是該序列的所有者。
  • 當前版本僅支持修改擁有者、歸屬列和最大值。若要修改其他參數,可以刪除重建,並用Setval函數恢復當前值。
  • ALTER SEQUENCE MAXVALUE不支持在事務、函數和存儲過程中使用。
  • 修改序列的最大值後,會清空該序列在所有會話的cache。
  • ALTER SEQUENCE會阻塞nextval、setval、currval和lastval的調用。

語法格式

修改序列最大值或歸屬列
ALTER SEQUENCE [ IF EXISTS ] name 
    [ MAXVALUE maxvalue | NO MAXVALUE | NOMAXVALUE ]
    [ OWNED BY { table_name.column_name | NONE } ] ;
修改序列的擁有者
ALTER SEQUENCE [ IF EXISTS ] name OWNER TO new_owner;

參數說明

  • name

將要修改的序列名稱。

  • IF EXISTS

當序列不存在時使用該選項不會出現錯誤消息,僅有一個通知。

  • MAXVALUE maxvalue | NO MAXVALUE

序列所能達到的最大值。如果聲明瞭NO MAXVALUE,則遞增序列的缺省值爲263-1,遞減序列的缺省值爲-1。NOMAXVALUE等價於NO MAXVALUE。

  • OWNED BY

將序列和一個表的指定字段進行關聯。這樣,在刪除那個字段或其所在表的時候會自動刪除已關聯的序列。

如果序列已經和表有關聯後,使用這個選項後新的關聯關係會覆蓋舊的關聯。

關聯的表和序列的所有者必須是同一個用戶,並且在同一個模式中。

使用OWNED BY NONE將刪除任何已經存在的關聯。

new_owner

序列新所有者的用戶名。用戶要修改序列的所有者,必須是新角色的直接或者間接成員,並且那個角色必須有序列所在模式上的CREATE權限。

示例

將序列serial的最大值修改爲200:
ALTER SEQUENCE serial MAXVALUE 200;
創建一個表,定義默認值:
CREATE TABLE T1(C1 bigint default nextval('serial'));
將序列serial的歸屬列變爲T1.C1:
ALTER SEQUENCE serial OWNED BY T1.C1;

5. 刪除序列

使用DROP SEQUENCE命令刪除一個序列。 例如,將刪除名爲seq1的序列:

DROP SEQUENCE seq1;

5.1 DROP SEQUENCE語句的使用方法

DROP SEQUENCE用於從當前數據庫裏刪除序列。

 

注意事項

只有序列的所有者或者系統管理員才能刪除。

語法格式

 

DROP SEQUENCE [ IF EXISTS ] {[schema.]sequence_name} [ , ... ] [ CASCADE | RESTRICT ];

參數說明

  • IF EXISTS

如果指定的序列不存在,則發出一個notice而不是拋出一個錯誤。

  • name

序列名稱。

  • CASCADE

級聯刪除依賴序列的對象。

  • RESTRICT
如果存在任何依賴的對象,則拒絕刪除序列。此項是缺省值。

6. SEQUENCE相關函數

序列函數爲用戶從序列對象中獲取後續的序列值提供了簡單的多用戶安全的方法。DWS目前支持以下SEQUENCE函數:

6.1 nextval(regclass)

nextval(regclass)用於遞增序列並返回新值。
返回類型:bigint
nextval函數有兩種調用方式(其中第二種調用方式兼容Oracle的語法,目前不支持Sequence命名中有特殊字符"."的情況),調用方式如下:

示例1:
postgres=# SELECT nextval('seqDemo'); 
 nextval
---------
    2
(1 row)
示例2:
postgres=# SELECT seqDemo.nextval; 
 nextval
---------
    2
(1 row)

注意事項

爲了避免從同一個序列獲取值的併發事務被阻塞, nextval操作不會回滾;也就是說,一旦一個值已經被抓取, 那麼就認爲它已經被用過了,並且不會再被返回。 即使該操作處於事務中,當事務之後中斷,或者如果調用查詢結束不使用該值,也是如此。這種情況將在指定值的順序中留下未使用的"空洞"。 因此,GaussDB(DWS)序列對象不能用於獲得"無間隙"序列。

如果nextval被下推到DN上時,各個DN會自動連接GTM,請求next values值,例如(insert into t1 select xxx,t1某一列需要調用nextval函數),由於GTM上有最大連接數爲8192的限制,而這類下推語句會導致消耗過多的GTM連接數,因此對於這類語句的併發數目限制爲7000(其它語句需要佔用部分連接)/集羣DN數目。

6.2 currval(regclass)

currval(regclass)用於返回當前會話裏最近一次nextval返回的指定的sequence的數值。如果當前會話還沒有調用過指定的sequence的nextval,那麼調用currval將會報錯。需要注意的是,這個函數在默認情況下是不支持的,需要通過設置enable_beta_features爲true之後,才能使用這個函數。同時在設置enable_beta_features爲true之後,nextval()函數將不支持下推。
返回類型:bigint
currval函數有兩種調用方式(其中第二種調用方式兼容Oracle的語法,目前不支持Sequence命名中有特殊字符"."的情況),調用方式如下:

示例1:
postgres=# SELECT currval('seq1'); 
 currval
---------
    2
(1 row)
示例2:
postgres=# SELECT seq1.currval seq1; 
 currval
---------
    2
(1 row)

6.3 lastval()

lastval()用於返回當前會話裏最近一次nextval返回的數值。這個函數等效於currval,只是它不用序列名爲參數,它抓取當前會話裏面最近一次nextval使用的序列。如果當前會話還沒有調用過nextval,那麼調用lastval將會報錯。

需要注意的是,lastval()函數在默認情況下是不支持的,需要通過設置enable_beta_features或者lastval_supported爲true之後,才能使用這個函數。同時這種情況下,nextval()函數將不支持下推。

返回類型:bigint

示例:

postgres=# SELECT lastval(); 
 lastval
---------
    2
(1 row)

6.4 setval(regclass, bigint)

setval(regclass, bigint)用於設置序列的當前數值。

返回類型:bigint

示例:

postgres=# SELECT setval('seqDemo',1);
 setval
--------
    1
(1 row)

6.5 setval(regclass, bigint, boolean)

setval(regclass, bigint, boolean)用於設置序列的當前數值以及is_called標誌。

返回類型:bigint

示例:

postgres=# SELECT setval('seqDemo',1,true);
 setval
--------
    1
(1 row)

 

注意事項

Setval後當前會話及GTM上會立刻生效,但如果其他會話有緩存的序列值,只能等到緩存值用盡才能感知Setval的作用。所以爲了避免序列值衝突,setval要謹慎使用。因爲序列是非事務的,setval造成的改變不會由於事務的回滾而撤銷。

 

7. 注意事項

新序列值的產生是靠GTM維護的,默認情況下,每申請一個序列值都要向GTM發送一次申請,GTM在當前值的基礎上加上步長值作爲產生的新值返回給調用者。GTM作爲全局唯一的節點,勢必成爲性能的瓶頸,所以對於需要大量頻繁產生序列號的操作,如使用Bulkload工具進行數據導入場景,是非常不推薦產生默認序列值的。比如,在下面所示的場景中, INSERT FROM SELECT語句的性能會非常慢。

CREATE SEQUENCE newSeq1;
CREATE TABLE newT1
           ( 
             id   int not null default nextval('newSeq1'), 
             name text
            );
INSERT INTO newT1(name) SELECT name from T1;

可以提高性能的寫法是(假設T1表導入newT1表中的數據爲10000行):

INSERT INTO newT1(id, name) SELECT id,name from T1;
SELECT SETVAL('newSeq1',10000);

序列操作函數nextval(),setval() 等均不支持回滾。另外setval設置的新值,會對當前會話的nextval立即生效,但對其他會話,如果定義了cache,不會立即生效,在用盡所有緩存的值後,其變動才被其他會話感知。所以爲了避免產生重複值,要謹慎使用setval,設置的新值不能是已經產生的值或者在緩存中的值。

如果必須要在bulkload場景下產生默認序列值,則一定要爲newSeq1定義足夠大的cache,並且不要定義Maxvalue或者Minvalue。數據庫會試圖將nextval(‘sequence_name’)的調用下推到Data Node,以提高性能。 目前GTM對併發的連接請求是有限制的,當Data Node很多時,將產生大量併發連接, 這時一定要控制bulkload的併發數目,避免耗盡GTM的連接資源。如果目標表爲複製表(DISTRIBUTE BY REPLICATION)時下推將不能進行。當數據量較大時,這對數據庫將是個災難。除了性能問題之外,空間也可能會劇烈膨脹,在導入結束後,需要用vacuum full來恢復。最好的方式還是如上建議的,不要在bulkload的場景中產生默認序列值。

另外,序列創建後,在每個節點上都維護了一張單行表,存儲序列的定義及當前值,但此當前值並非GTM上的當前值,只是保存本節點與GTM交互後的狀態。如果其他節點也向GTM申請了新值,或者調用了Setval修改了序列的狀態,不會刷新本節點的單行表,但因每次申請序列值是向GTM申請,所以對序列正確性沒有影響。

8. 使用案例

DWS如何重置自增列的開始序號?

使用函數setval(regclass, bigint)對自增列值進行重置。

示例:

將seqDemo列的開始序號重置爲1:
 
postgres=# SELECT setval('seqDemo',1);
 setval
--------
    1
(1 row)

DWS如何確定sequence和哪個表有關聯?

先在pg_class查找目標sequence的oid,然後在pg_depend根據oid查依賴該sequence的對象

示例:

先創建自增序列seq1和依賴seq1的表T2:
 
postgres=# CREATE SEQUENCE seq1 cache 100;
 
postgres=# CREATE TABLE T2 
postgres-# ( 
postgres(#     id   int not null default nextval('seq1'),
postgres(#     name text
postgres(# );
 
根據seq1從表pg_class、pg_depend聯合查詢到依賴表T2的oid:
 
postgres=# select * from pg_depend where objid = (select oid from pg_class where relname = 'seq1')
 classid |   objid    | objsubid | refclassid | refobjid | refobjsubid | deptype 
---------+------------+----------+------------+----------+-------------+---------
    1259 | 2147485853 |        0 |       2615 |     2200 |           0 | n
(1 row)

如何查詢序列的last_value?

由於SEQUENCE在自增過程中並不是嚴格逐個增加,因此序列號中會存在空端數據,所以last_value本身並沒有實際意義,可以採用函數lastval()進行查詢。

示例:
postgres=# SELECT lastval(); 
 lastval
---------
    2
(1 row)

 

注意事項

如果當前會話還沒有調用過nextval,那麼調用lastval將會報錯。此外,lastval()函數在默認情況下是不支持的,需要通過設置enable_beta_features或者lastval_supported爲true之後,才能使用這個函數。同時這種情況下,nextval()函數將不支持下推。

 

如何查詢SEQUENC的當前最新值?

通過currval函數可以查詢SEQUENC的當前最新值。

示例:
currval函數有兩種調用方式(其中第二種調用方式兼容Oracle的語法,目前不支持Sequence命名中有特殊字符"."的情況),調用方式如下:
示例1:
postgres=# SELECT currval('seq1'); 
 currval
---------
    2
(1 row)
 
示例2:
postgres=# SELECT seq1.currval seq1; 
 currval
---------
    2
(1 row)

如何解決SEQUENC取值超出範圍的問題?

1.可以在創建SEQUENC時設置CYCLE字段,從而使得序列達到maxvalue或者minvalue後可循環並繼續下去。但需要注意,若定義序列爲CYCLE,則不能保證序列的唯一性。

2.通過調用setval(regclass, bigint)函數對序列取值進行重置。

9. 總結

本文介紹了SEQUENCE的使用場景和相關的函數的使用方法,並對使用SEQUENCE過程中遇到的常見問題及解決方法進行了彙總。

點擊關注,第一時間瞭解華爲雲新鮮技術~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章