Gel 标准库 sequence 类型:自增序列的声明、推进、重置与并发语义
2026/9/23 2:47:18 网站建设 项目流程

Gel 标准库 sequence 类型:自增序列的声明、推进、重置与并发语义

【免费下载链接】edgedbGel supercharges Postgres with a modern data model, graph queries, Auth & AI solutions, and much more.项目地址: https://gitcode.com/gh_mirrors/ed/edgedb

std::sequence是 Gel(EdgeDB)标准库中内置的抽象标量类型,用于生成基于int64的自动递增整数序列。本文围绕 docs/reference/stdlib/sequence.rst 展开,完整讲解如何用sequence声明自增属性、通过sequence_next()/sequence_reset()推进与重置序列、如何用introspect或运行时查询指定序列,以及高并发下序列不回滚、可能产生间隙的事务语义;同时结合仓库源码与测试用例,剖析其底层基于 PostgreSQLnextval/setval的实现原理。读完本文,你将能够在自己的 Gel 模式中安全、高效地设计票号、订单号等自增业务标识。

一、认识std::sequence:int64 自增序列

std::sequence是一个抽象标量类型(abstract scalar type),在标准库中定义如下(见 edb/lib/std/10-scalars.edgeql):

CREATE ABSTRACT SCALAR TYPE std::sequence EXTENDING std::int64;

也就是说,sequence本质上是int64的扩展,任何extending sequence的自定义标量类型都是一个“序列类型”,其值由系统按递增顺序自动生成。它天然适合作为自动递增的数值属性的基类型。

1.1 声明自增属性

在 SDL 模式中,你可以通过定义一个扩展sequence的标量类型,再把它用作某个对象类型的属性:

scalar type TicketNo extending sequence; type Ticket { number: TicketNo { constraint exclusive; } }

这是官方文档给出的标准用法:TicketNo扩展sequenceTicket.number使用TicketNo并加上exclusive约束以保证唯一。仓库测试模式中也大量采用这种写法,例如 tests/schemas/dump03_default.esdl 中的scalar type MySeq extending sequence;scalar type MyPristineSeq extending sequence;,以及 tests/schemas/insert.esdl 中的scalar type int_seq8_t extending sequence;

1.2 序列绑定到类型而非属性

一个关键语义是:序列绑定的是标量类型,而不是属性

因此,如果多个属性共享同一个序列类型,它们会共享同一个计数器。例如,如果OrderInvoice两个类型都使用DocNo(扩展sequence)作为编号属性,那么它们产出的编号来自同一个递增序列,互相穿插、不重复也不独立。

最佳实践:如果你需要 N 个互不干扰的计数器,就应定义 N 个不同的extending sequence标量类型,每个类型对应一条独立的序列。

二、sequence_next():推进序列并取值

sequence_next(seq: schema::ScalarType) -> int64将给定序列推进到下一个值,并返回该值。

  • 原子性:序列推进是原子的,每个并发会话和事务都会获得互不相同的序列值,不会出现两个会话拿到同一个号。
  • 调用方式:需要传入一个schema::ScalarType对象,通常是introspect的结果。
db> select sequence_next(introspect MySequence); {11}

从源码看,sequence_next的实现直接映射到 PostgreSQL 的nextval(见 edb/lib/schema.edgeql):

SELECT pg_catalog.nextval( pg_catalog.quote_ident(sn.schema) || '.' || pg_catalog.quote_ident(sn.name) ) FROM ROWS FROM (edgedb_VER.get_user_sequence_backend_name("seq")) AS sn(schema text, name text)

其中get_user_sequence_backend_name负责把 Gel 层的序列标量类型解析为后端 PostgreSQL 中实际的 sequence 对象(schema 名 + 序列名),nextval是 PostgreSQL 原生的、并发安全的序列推进函数。这意味着 Gel 的序列语义与 PostgreSQL 序列高度一致——包括“不回滚”的行为(详见第五节)。

三、sequence_reset():重置序列

sequence_reset有两个重载形式(见 edb/lib/schema.edgeql):

std::sequence_reset(seq: schema::ScalarType) -> int64 std::sequence_reset(seq: schema::ScalarType, val: int64) -> int64

两个形式都会返回当前重置后的值

3.1 单参数形式:重置到初始状态

sequence_reset(seq)将序列重置到初始状态,下一次sequence_next将返回序列的第一个值(默认从 1 开始):

db> select sequence_reset(introspect MySequence); {1} db> select sequence_next(introspect MySequence); {1}

其底层实现是从pg_catalog.pg_sequences读取该序列的start_value,再以is_called = false调用pg_catalog.setval,这样下一次nextval就会从起始值重新开始(见 edb/lib/schema.edgeql)。

3.2 双参数形式:设置当前值

sequence_reset(seq, val)将序列的当前值设置为val,下一次sequence_next返回的是你传入值之后的下一个值(即val + 1,除非另有设置):

db> select sequence_reset(introspect MySequence, 22); {22} db> select sequence_next(introspect MySequence); {23}

底层实现是以is_called = true调用pg_catalog.setval,即把“已取到的最后一个值”记为val,因此下一次取号自然从val + 1继续(见 edb/lib/schema.edgeql)。

四、指定序列的最佳实践:introspect 与运行时查询

无论sequence_next还是sequence_reset,都需要传入一个schema::ScalarType对象来指明操作哪条序列。文档专门给出了两种场景下的推荐写法(见原文档 docs/reference/stdlib/sequence.rst 中ref_std_specifying_sequence一节)。

4.1 序列类型编译期已知:优先用introspect

如果序列类型在写查询时已经确定、不会变化,推荐用introspect运算符直接引用类型:

select sequence_next(introspect MySequenceType); # or select sequence_next(introspect typeof MyObj.seq_prop);

两种写法都可行:直接introspect序列标量类型本身,或者通过typeof从某个属性反推其类型。这样做的意义在于:表达式对序列类型的引用会被正确追踪,从而保证模式层面的引用完整性(referential integrity)——编译器能够感知“这条查询依赖了哪个序列类型”,避免序列被误删或改名后查询静默失效。

仓库测试同样遵循这一风格,例如 tests/test_dump03.py 中:

"SELECT sequence_next(INTROSPECT TYPEOF Test.seq)" "SELECT sequence_next(INTROSPECT MyPristineSeq)"

4.2 序列类型运行时才确定:查询schema::ScalarType

如果序列类型必须由查询参数在运行时决定(例如用户传入序列名),introspect就派不上用场了。此时需要直接从schema::ScalarType集合中按名字查询:

with SeqType := ( select schema::ScalarType filter .name = <str>$seq_type_name ) select sequence_next(SeqType);

这里schema::ScalarType是 Gel 内省(introspection)体系中所有标量类型的抽象基类,$seq_type_name是运行时传入的完整类型名(如default::MySequence)。这种写法牺牲了一部分编译期引用追踪能力,但换来了动态选择序列的灵活性——适用于多租户、多计数器等按需切换序列的场景。

五、高并发与事务语义:序列不回滚,允许出现间隙

文档对序列的事务行为给出了明确的警告,这也是使用序列时最容易踩坑的地方:

  • sequence_next的执行永远不会回滚,即使包含它的事务最终被中止(abort),已经取走的序列值也不会归还。因此生成的序列可能出现间隙(gaps)——例如事务 A 取走 10、11 后回滚,事务 B 接着会取到 12,10 和 11 就永久空置。
  • 同样,sequence_reset的结果在事务回滚时也不会被撤销——重置操作一旦生效即持久化。

这一行为与 PostgreSQL 原生序列完全一致:nextval/setval属于非事务性操作(non-transactional),其副作用不随事务提交/回滚而改变。从源码可以确认,Gel 的序列函数就是直接透传调用 PostgreSQL 的nextval/setval(见 edb/lib/schema.edgeql),因此继承了这一语义。

5.1 对应用设计的影响

  • 不要假设编号连续。如果业务要求“编号必须是连续的”,序列方案并不合适,应改用应用层加锁计数或事后补号等方案。
  • 序列值适合做标识、不适合做严格顺序依据。若需要“编号即时间顺序”,在高并发 + 回滚场景下序列值无法保证与提交顺序严格一致。
  • 间隙是特性而非缺陷。间隙换来的是无锁竞争的高并发性能——nextval通过 PostgreSQL 内部机制原子递增,多个会话互不阻塞。

六、权限要求:sequence_reset 需要 DDL 权限

从权限模型看,sequence_nextsequence_reset的权限要求并不相同。在 edb/lib/sys.edgeql 中可以看到,标准库对两个sequence_reset重载都额外声明了:

ALTER FUNCTION std::sequence_reset(...) { SET required_permissions := sys::perm::ddl; };

也就是说,调用sequence_reset需要 DDL 级别的权限(因为它会修改序列的状态),而sequence_next没有这样的额外限制,普通数据操作即可调用。在权限敏感的应用中,应把重置序列的能力限制在管理员/迁移类角色上,普通用户只允许取号。

七、应用场景小结

场景推荐做法
自增业务编号(票号、订单号、流水号)定义scalar type XxxNo extending sequence;,属性加constraint exclusive
多个实体共享一个计数器多个属性使用同一个序列类型(注意编号会交叉)
多个独立计数器每个计数器单独定义一个extending sequence类型
编译期已知序列类型sequence_next(introspect MySeqType)introspect typeof Obj.prop
运行时动态选择序列select schema::ScalarType filter .name = <str>$name后传入函数
重置到起始值sequence_reset(seq),下次取号返回第一个值
从指定值继续sequence_reset(seq, 22),下次取号返回 23
需要严格无间隙编号序列方案不适用,需自行实现补号逻辑

八、总结

std::sequence为 Gel 提供了简洁而强大的自增序列能力:通过extending sequence声明序列类型并绑定属性,用sequence_next原子取号、用sequence_reset重置状态,配合introspect保证引用完整性,且底层由 PostgreSQL 的nextval/setval提供并发安全与持久化保证。需要始终牢记的是其“不回滚、有间隙”的事务语义,以及sequence_reset所需的 DDL 权限——理解了这些边界,你就能在设计高并发业务标识时做出正确取舍。

进一步阅读:可在 docs/reference/datamodel/properties.rst 了解属性与约束(constraint exclusive)的完整语义,在 docs/reference/stdlib/introspection.rst 所在的 docs/reference/stdlib 目录下查看schema::ScalarType等内省类型的说明,或在 edb/lib/std/10-scalars.edgeql 与 edb/lib/schema.edgeql 中直接阅读序列的声明与实现源码。

【免费下载链接】edgedbGel supercharges Postgres with a modern data model, graph queries, Auth & AI solutions, and much more.项目地址: https://gitcode.com/gh_mirrors/ed/edgedb

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询