PostgreSQL技术问答29 - Sequence序列

本文是《PostgreSQL技术问答》系列文章中的一篇。关于这个系列的由来,可以参阅开篇文章:

《PostgreSQL技术问答00 - Why Postgres》

文章的编号只是一个标识,在系列中没有明确的逻辑顺序和意义。读者进行阅读时,不用太关注这个方面。

本文讨论的内容是PostgreSQL中,高效生成序列化数字和ID的机制: Sequence,就是序列。

什么是Sequence

Sequence的英文意为"序列",就是一个有序的、可以单调递增的整数序列。

在Postgres中, Sequence是一个序列数字的生成器。在使用前,需要先创建一个序列的实例,然后可以将这个实例作为数据类型,应用到数据库表字段定义当中。当然,也可以直接使用序列相关的操作方法,如从序列实例中获取下一个值或者当前值,来支持特定的业务应用需求。

在数据库应用中,序列技术最常用的场景,就是辅助生成一个数据库级别的不重复的编号,可以作为一个标识。这一就可以解决了数据库表中,记录主键或者唯一标识的问题。

如果没有系统级内置的序列机制的话,在程序级别的实现是比较麻烦的。例如,需要查询表中,最大的记录ID,然后加1作为新值;或者随机生成一个ID,再检查表中是否有重复的信息;或者使用一个ID记录表,批量生成后,使用并标记可用的ID;再有就是使用UUID的机制。这些实现,相对而言都比较麻烦,而且代价也比较高昂。

Postgres中,如何使用序列

Postgres官方技术文档的序列章节的链接如下:

www.postgresql.org/docs/16/sql...

在Postgres中使用序列,一般涉及以下相关操作。

创建序列实例

以下的语句,可以在数据库中,创建一个序列实例:

sql 复制代码
CREATE SEQUENCE sq_common
  START 1
  INCREMENT 1
  MINVALUE 1
  MAXVALUE 200000000
  CACHE 10;
CREATE SEQUENCE

-- 简单写法
CREATE SEQUENCE sq_common;

-- 完整的语法
CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] name
    [ AS data_type ]
    [ INCREMENT [ BY ] increment ]
    [ MINVALUE minvalue | NO MINVALUE ] [ MAXVALUE maxvalue | NO MAXVALUE ]
    [ START [ WITH ] start ] [ CACHE cache ] [ [ NO ] CYCLE ]
    [ OWNED BY { table_name.column_name | NONE } ]

这里的要点如下:

  • 语句定义了一个名为 sq_common的序列实例
  • 可以定义起始值(默认1)、最小值、最大值、步进值(默认为1)
  • 简单的写法,只需要指定一个名称
  • 缓存值和序列生成的缓存机制,可以帮助提高序列生成和使用的性能
  • 在Postgres中,本质上序列的值就是一个bigint整数,其限制就是bigint的取值范围,当然用户可以指定其范围
  • 可以使用CYCLE指定序列是否可以循环取值(用完后从头开始)

数据库字段

使用以下语句和方法,创建一个使用序列的数据库表:

sql 复制代码
CREATE TABLE my_table (
  id INTEGER PRIMARY KEY DEFAULT NEXTVAL('sq_common'),
  name VARCHAR(50)
);

\d my_table
                                Table "public.my_table"
 Column |         Type          | Collation | Nullable |            Default             
--------+-----------------------+-----------+----------+--------------------------------
 id     | integer               |           | not null | nextval('sq_common'::regclass)
 name   | character varying(50) |           |          | 
Indexes:
    "my_table_pkey" PRIMARY KEY, btree (id)

从描述信息来看,和想象的不同,并不是说直接指定字段使用这个序列实例,而是先设置字段类型是interger,然后指定其默认值,来调用一个nextval()方法,方法的参数就是序列实例的名称。这一,在插入新行的时候,这个字段如果没有设置值,则会调用序列方法,获取序列中下一个值,作为这个字段的值。

注意这里nextval中的参数的完整定义,类型是regclass。也说就是,实际nextval的参数,是一个对象,regclass转换可以使用名称来映射这个对象,作为参数。

在这个例子中,利用序列实例,定义了一个主键字段。使用同样的道理,还可以利用序列定义其他的不重复编码的值的字段。此外,由于这种使用方式,我们也可以在不同的表中,共享这个序列。

序列值操作

上面定义数据库表字段的示例中,我们已经看到,可以使用相关的函数来获得序列实例中的下一个值。关于这种类似的序列操作的函数包括:

  • nextval() 获取序列实例的下一个序列值,而且当函数调用时,序列实例中的当前值会自动步进
  • currval() 获取序列实例当前值,同时序列不会步进
  • setval() 给序列设置当前值,可选获取下一个值或者当前值
  • lastval() 获取当前session中,最后一个由序列生成的值(注意不需要指定实例)

下面是相关代码示例:

sql 复制代码
-- 下一值
defaultdb=> select nextval('sq_common');
 nextval 
---------
       3
(1 row)

-- 当前值
defaultdb=> select currval('sq_common');
 currval 
---------
       3
(1 row)

-- 设置值
defaultdb=> SELECT setval('sq_common', 42); 
 setval 
--------
     42
(1 row)

defaultdb=> SELECT setval('sq_common', 42, false); 
 setval 
--------
     42
(1 row)


-- 利用序列来编码
 select 'CODE-' ||  LPAD('' || nextval('sq_common'),5,'0');
  ?column?  
------------
 CODE-00007
(1 row)

序列实例管理

在序列实例创建之后,可以使用相关语句对其进行管理。

  • ALTER SEQUENCE 修改序列实例的起始、步进、最大值和最小值

简单示例如下:

sql 复制代码
-- 修改起始值
alter sequence sq_common  start 1001;
ALTER SEQUENCE

-- 删除实例
drop sequence sq_common;
ERROR:  cannot drop sequence sq_common because other objects depend on it
DETAIL:  default value for column id of table my_table depends on sequence sq_common
HINT:  Use DROP ... CASCADE to drop the dependent objects too.

这里修改了start值后,其实对序列当前值并没有直接的影响。也许如果可以循环,它会以这个值作为起始吧。如果确实需要修改序列当前的值,需要使用setval方法。

  • DROP SEQUENCE 删除序列实例

要注意删除的时候,对使用该实例的表的影响(上例)。例如,如果序列在被使用的时候,其实是不能直接删除的。相关的错误信息可以清晰的指出操作失败的原因和位置。

如何查询序列的状态

有趣的是,作为一个数据库对象,可以直接使用select语句,来查询序列实例的状态。

sql 复制代码
SELECT * FROM sq_common;
 last_value | log_cnt | is_called 
------------+---------+-----------
          3 |      30 | t
(1 row)

在Postgres中,有一个serial数据类型,和序列是什么关系

笔者理解,这实际上是一个语法糖,或者批处理标识。目的是简化表的定义,因为这是一种比较常见的需求。

具体操作过程应该是这样的。系统如果发现定义字段的类型是serial,在创建表时,就会使用默认设置和命名规则,先创建一个序列实例,然后使用这个序列实例名称,设置到字段默认值的定义当中。

比如上面的例子,可以简化为:

sql 复制代码
defaultdb=> CREATE TABLE my_table2 (
  id SERIAL PRIMARY KEY,
  name VARCHAR(50)
);
CREATE TABLE

defaultdb=> \d my_table2;
                                   Table "public.my_table2"
 Column |         Type          | Collation | Nullable |                Default                
--------+-----------------------+-----------+----------+---------------------------------------
 id     | integer               |           | not null | nextval('my_table2_id_seq'::regclass)
 name   | character varying(50) |           |          | 
Indexes:
    "my_table2_pkey" PRIMARY KEY, btree (id)

这样就简单了很多,而且效果基本一样。

创建Sequence语句中,有两个关键字Temp和Unlogged是做什么的

Temp关键字,可以指定当前创建的序列是一个临时序列,只对当前会话有效,有点像Temp Table的意思。

Unlogged关键字,该设置将创建为不记录的序列。也就是说,对序列所做的修改,不会写到日志中。如果系统崩溃或者意外关闭,这个序列会重置到初始状态。但意外的是,这个设置并没有提供显著的性能优势(比如unlogged insert),主要用于配合非日志记录的表来使用。实际应用中,应该没有机会接触到这种场景。

使用序列需要注意什么问题

笔者在日常使用序列,觉得有一些观点和经验可以分享一些:

  • SQL语句中,是可以直接使用序列函数的
  • 如果没有其他的特别原因,可以考虑共享序列
  • 序列不仅仅可以用于标识字段,也可以通过配合字符串函数,作为不重复编码的生成方式
  • 在做数据迁移的时候,要特别注意序列实例一般是无法迁移的,可能需要一些手动的处理和操作
  • 不同系统中的序列,特别是迁移的序列的值,要特别注意其一致性
  • 需要理解,序列对象实例,和数据库字段的值没有直接的逻辑联系,只是借助了序列操作结果而已,所以特别要注意在修改序列状态的时候,可能对字段值造成的取值冲突
  • 序列值是可以不连续的(修改步长不为1),虽然好像没有特别的理由这么做
  • 作为系统级别的实现,应当优先使用序列作为不重复值生成机制

小结

在Postgres中,序列是一种系统级别的高效生成序列化数组的机制。本文讨论了这个机制相关实现和操作的过程,相关数据类型和数据字段的定义,相关函数的使用方法和场景,以及需要注意的问题等内容。

相关推荐
QX_hao3 小时前
【Go】--map和struct数据类型
开发语言·后端·golang
MC丶科4 小时前
【SpringBoot 快速上手实战系列】5 分钟用 Spring Boot 搭建一个用户管理系统(含前后端分离)!新手也能一次跑通!
java·vue.js·spring boot·后端
G探险者4 小时前
为何一个系统上线要经过N轮测试?带你看懂企业级发布体系
后端
TDengine (老段)5 小时前
TDengine 数学函数 DEGRESS 用户手册
大数据·数据库·sql·物联网·时序数据库·iot·tdengine
TDengine (老段)5 小时前
TDengine 数学函数 GREATEST 用户手册
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
安当加密6 小时前
云原生时代的数据库字段加密:在微服务与 Kubernetes 中实现合规与敏捷的统一
数据库·微服务·云原生
lang201509286 小时前
Spring Boot 入门:5分钟搭建Hello World
java·spring boot·后端
爱喝白开水a6 小时前
LangChain 基础系列之 Prompt 工程详解:从设计原理到实战模板_langchain prompt
开发语言·数据库·人工智能·python·langchain·prompt·知识图谱
想ai抽6 小时前
深入starrocks-多列联合统计一致性探查与策略(YY一下)
java·数据库·数据仓库