如何在 SpacetimeDB 中用自增列生成唯一的主键行 ID

发布时间:2026/9/13 21:46:22
如何在 SpacetimeDB 中用自增列生成唯一的主键行 ID 如何在 SpacetimeDB 中用自增列生成唯一的主键行 ID【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB在 SpacetimeDB 的模块代码中给表里的每一行生成一个不需要手工分配、又不重复的行 ID是建表时很常见的诉求。SpacetimeDB 提供自增列auto-increment column当你在自增列插入一个零值时数据库会从内部序列分配下一个值并把分配后的行返回给你。把自增列同时标记为主键primary key就得到一个自动分配的唯一定位列更新和删除也按它定位行。本文以 Rust 模块代码为主路径给出定义列、插入行、用 CLI 验证的完整操作TypeScript / C# / C 写法作为可选分支。适用前提是你在本地运行模块开发环境文中事实均取自 自增列文档。准备环境按 Rust 快速开始 的要求环境需要已安装 Rust已安装 SpacetimeDB CLI。在项目目录运行spacetime dev --template basic-rs该命令会启动本地 SpacetimeDB 服务器、编译并发布模块、生成客户端 bindings。模块代码在spacetimedb/src/lib.rs。注意 reducers 是写入数据库的唯一途径所以插入行必须写在一个 reducer 里。定义自增主键列在模块中定义表时把#[primary_key]和#[auto_inc]同时加在同一个整型字段上Rust 主路径use spacetimedb::{ReducerContext, Table}; #[spacetimedb::table(accessor post, public)] pub struct Post { #[primary_key] #[auto_inc] id: u64, title: String, } #[spacetimedb::reducer] fn add_post(ctx: ReducerContext, title: String) - Result(), String { // Pass 0 for the auto-increment field let inserted ctx.db.post().insert(Post { id: 0, title }); // inserted.id now contains the assigned value log::info!(Created post with id: {}, inserted.id); Ok(()) }自增列必须是整数类型。Rust 支持i8、i16、i32、i64、i128、u8、u16、u32、u64、u128。示例中的u64是文档给出的常规选择对应序列从 1 开始最大值为 2^64 - 1。其他语言的同款定义按你实际的模块语言选用TypeScriptconst post table( { name: post, public: true }, { id: t.u64().primaryKey().autoInc(), title: t.string(), } ); const spacetimedb schema({ post }); export default spacetimedb; export const addPost spacetimedb.reducer({ title: t.string() }, (ctx, { title }) { // Pass 0 for the auto-increment field const inserted ctx.db.post.insert({ id: 0n, title }); // inserted.id now contains the assigned value console.log(Created post with id: ${inserted.id}); });注意方法名是.autoInc()不存在autoIncrement。整型可选t.i8()到t.u256()的全部整数类型。C#[SpacetimeDB.Table(Accessor Post, Public true)] public partial struct Post { [SpacetimeDB.PrimaryKey] [SpacetimeDB.AutoInc] public ulong Id; public string Title; } [SpacetimeDB.Reducer] public static void AddPost(ReducerContext ctx, string title) { // Pass 0 for the auto-increment field var inserted ctx.Db.Post.Insert(new Post { Id 0, Title title }); // inserted.Id now contains the assigned value Log.Info($Created post with id: {inserted.Id}); }C建表注册后调用FIELD_PrimaryKeyAutoInc(post, id)宏标记。触发值插入 0 才触发自增自增机制只在自增列为零值时激活插入非零值时SpacetimeDB 直接使用你给的值不生成新值// Triggers auto-increment: id will be assigned automatically ctx.db.post().insert(Post { id: 0, title: Hello.into() })?; // Does NOT trigger auto-increment: id will be 42 ctx.db.post().insert(Post { id: 42, title: World.into() })?;这个行为意味着你可以把带已知 ID 的存量数据直接插入自增主键表新行继续走自增分配二者不冲突。运行并验证在另一个终端进入项目目录后调用 reducer 并查询表。以下命令与输出取自自增列文档其中mydb是文档示例使用的实例名insert_user/user是文档示例中的 reducer 名与表名按你实际的 reducer、表与实例名替换# Insert 3 users $ spacetime call mydb insert_user Alice $ spacetime call mydb insert_user Bob $ spacetime call mydb insert_user Carol $ spacetime sql mydb SELECT * FROM user user_id | name ---------------- 1 | Alice 2 | Bob 3 | Carol以上输出为文档示例结果第一个用户得到user_id1u64自增列的序列默认从 1 开始依次递增。注意快速开始文档中在项目目录下验证的命令形式不带实例名spacetime call add Alice、spacetime sql SELECT * FROM person两处文档形式略有差异按你的实际运行环境使用对应形式。重启、缺口与边界行为验证通过之后有几个文档明确给出的行为边界直接影响你对 ID 的预期重启会跳到下一个分配边界。自增底层使用序列sequence参考 PostgreSQL 序列设计参数为start、min_value、max_value、increment。出于崩溃恢复考虑序列按4096 个值一批预分配并持久化分配边界而不是每次自增都落盘。数据库重启后从下一个边界继续可能跳过已分配未使用的值。文档示例# Database restarts... # Insert another user $ spacetime call mydb insert_user Dave $ spacetime sql mydb SELECT * FROM user user_id | name ---------------- 1 | Alice 2 | Bob 3 | Carol 4097 | Dave # Jumped to next allocation boundary这段同样是文档示例输出。它说明 ID 保证不重复但不保证连续文档同时指出即使单个 reducer 内连续两次插入得到的值也可能不是相邻的例如 1 和 3。到达上限会回绕。序列到达max_value后从min_value重新开始。如果自增列同时是主键或带唯一约束回绕后插入重复值会失败。u64量级下实际几乎不会回绕但如果你用u8、u16这类小类型或插入量极大文档要求你为序列耗尽做规划。不能与默认值组合。自增列不能同时使用 default value因为两者都试图自动填充该列。需要严格连续编号时的替代做法如果业务上要求编号严格连续、不允许缺口文档给出的方案是放弃自增列改用一张独立的计数器表在同一次事务里完成计数更新 行插入use spacetimedb::{ReducerContext, Table}; #[derive(Clone)] #[spacetimedb::table(accessor counter, public)] pub struct Counter { #[primary_key] name: String, value: u64, } #[spacetimedb::table(accessor invoice, public)] pub struct Invoice { #[primary_key] invoice_number: u64, amount: u64, } #[spacetimedb::reducer] fn create_invoice(ctx: ReducerContext, amount: u64) - Result(), String { // Get or create the counter let mut counter ctx.db.counter().name().find(invoice.to_string()) .unwrap_or(Counter { name: invoice.to_string(), value: 0 }); // Increment and update counter.value 1; ctx.db.counter().name().update(counter.clone()); // Use the counter value as the invoice number ctx.db.invoice().insert(Invoice { invoice_number: counter.value, amount, }); Ok(()) }由于计数器更新和行插入发生在同一事务中该模式保证编号严格连续。相关文档自增列完整文档序列参数、回绕、崩溃恢复与并发缺口的全部细节约束文档主键规则每张表至多一个主键、主键即唯一索引、自增主键作为常见主键模式的说明以及主键与唯一列的更新行为差异Rust 快速开始本地项目创建与 CLI 验证命令。【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考