ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

Gel 标准库 sequence 类型:自增序列的声明、推进、重置与并发语义 Gel 标准库 sequence 类型自增序列的声明、推进、重置与并发语义【免费下载链接】edgedbGel supercharges Postgres with a modern data model, graph queries, Auth AI solutions, and much more.项目地址: https://gitcode.com/gh_mirrors/ed/edgedbstd::sequence是 GelEdgeDB标准库中内置的抽象标量类型用于生成基于int64的自动递增整数序列。本文围绕 docs/reference/stdlib/sequence.rst 展开完整讲解如何用sequence声明自增属性、通过sequence_next()/sequence_reset()推进与重置序列、如何用introspect或运行时查询指定序列以及高并发下序列不回滚、可能产生间隙的事务语义同时结合仓库源码与测试用例剖析其底层基于 PostgreSQLnextval/setval的实现原理。读完本文你将能够在自己的 Gel 模式中安全、高效地设计票号、订单号等自增业务标识。一、认识std::sequenceint64 自增序列std::sequence是一个抽象标量类型abstract scalar type在标准库中定义如下见 edb/lib/std/10-scalars.edgeqlCREATE 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 序列绑定到类型而非属性一个关键语义是序列绑定的是标量类型而不是属性。因此如果多个属性共享同一个序列类型它们会共享同一个计数器。例如如果Order和Invoice两个类型都使用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.edgeqlSELECT 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.edgeqlstd::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 接着会取到 1210 和 11 就永久空置。同样sequence_reset的结果在事务回滚时也不会被撤销——重置操作一旦生效即持久化。这一行为与 PostgreSQL 原生序列完全一致nextval/setval属于非事务性操作non-transactional其副作用不随事务提交/回滚而改变。从源码可以确认Gel 的序列函数就是直接透传调用 PostgreSQL 的nextval/setval见 edb/lib/schema.edgeql因此继承了这一语义。5.1 对应用设计的影响不要假设编号连续。如果业务要求“编号必须是连续的”序列方案并不合适应改用应用层加锁计数或事后补号等方案。序列值适合做标识、不适合做严格顺序依据。若需要“编号即时间顺序”在高并发 回滚场景下序列值无法保证与提交顺序严格一致。间隙是特性而非缺陷。间隙换来的是无锁竞争的高并发性能——nextval通过 PostgreSQL 内部机制原子递增多个会话互不阻塞。六、权限要求sequence_reset 需要 DDL 权限从权限模型看sequence_next与sequence_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),仅供参考
返回列表