You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

高并发场景下,用SpannerClient Mutation插入后获取主键的方案咨询

解决方案与最佳实践

方案1:用DML INSERT ... THEN RETURN替代Mutation

这是最直接的原生解决方案,利用Spanner支持的INSERT ... THEN RETURN语法,在插入实体的同时直接获取引擎生成的bit reverted sequence主键,完美匹配你的需求。

示例Kotlin代码:

val insertSql = """
    INSERT INTO your_entity_table (field1, field2)
    VALUES (@fieldVal1, @fieldVal2)
    THEN RETURN id
"""
val params = mapOf("fieldVal1" to "sample_val1", "fieldVal2" to 123)
val generatedId = databaseClient
    .sql(insertSql)
    .bindAll(params)
    .execute()
    .single()
    .getLong("id")
// 直接用generatedId访问刚插入的实体
  • 优势:无需额外同步逻辑,一步完成写入+主键获取,性能足以支撑数千QPS的并发需求;批量插入时也能返回所有生成的主键。
  • 注意:如果你的代码之前大量使用Mutation,只需针对需要返回主键的写入场景切换为DML即可,两者可以共存。

方案2:客户端预生成唯一主键(替代自动序列)

如果想继续使用Mutation而不切换到DML,可以在客户端生成全局唯一的主键值,比如UUID(虽然bit reverted sequence是Spanner的优化序列,但UUID在高并发下也能保证唯一性,且无需依赖数据库生成)。

示例Kotlin代码:

val clientGeneratedId = UUID.randomUUID().toString()
val mutation = Mutation.newInsertBuilder("your_entity_table")
    .set("id").to(clientGeneratedId)
    .set("field1").to("sample_val1")
    .build()
databaseClient.write(listOf(mutation))
// 直接用clientGeneratedId访问实体
  • 优势:Mutation写入完成后立刻拥有主键,无需额外查询;代码风格统一,避免DML和Mutation的切换成本。
  • 变通方案:如果必须保留Spanner自动生成的bit reverted sequence作为主键,可以在表中新增一个客户端可控的唯一索引列(比如client_unique_id),插入时生成并设置该列,之后通过client_unique_id查询实体获取主键。

方案3:利用Commit Timestamp做延迟查询(特殊场景)

如果业务允许短暂的查询延迟,可以在表中添加commit_timestamp列(定义为TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)),插入时不设置该值,由Spanner自动填充提交时间。之后客户端通过插入时的唯一业务属性+时间范围定位实体,获取主键。

示例:

// 插入时不设置commit_timestamp,由Spanner自动生成
val mutation = Mutation.newInsertBuilder("your_entity_table")
    .set("unique_business_field").to("only_one_this_val")
    .build()
databaseClient.write(listOf(mutation))

// 用业务属性+时间范围查询主键
val querySql = """
    SELECT id FROM your_entity_table
    WHERE unique_business_field = @bizVal AND commit_timestamp >= @startTime
"""
val params = mapOf(
    "bizVal" to "only_one_this_val",
    "startTime" to Instant.now().minusSeconds(5)
)
val generatedId = databaseClient
    .sql(querySql)
    .bindAll(params)
    .execute()
    .single()
    .getLong("id")
  • 优势:保持Mutation的写入性能,适合对写入延迟要求极高、查询可接受短暂等待的场景。
  • 注意:必须确保业务属性本身具备唯一性(或结合时间范围后唯一),否则可能查到多条数据。

不推荐同步ID空间方案的原因

你提到的多实例同步ID空间方案,会引入分布式锁、ID分配服务等额外复杂度,不仅增加运维成本,还可能成为性能瓶颈——完全没必要,Spanner原生提供的方案已经能更高效地解决问题。

内容的提问来源于stack exchange,提问作者Matteo Bertucelli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 21:12:53