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

如何在Cloud Spanner表中限制每个用户仅存20条最新搜索记录?

Cloud Spanner 单用户存储行数限制方案

单事务还是分事务?

直接给结论:高频写入场景下,分事务比单事务更靠谱,原因如下:

  • 单事务把写入新记录、查询计数、删除旧记录绑定在一起,事务持有锁的时间过长,百万级请求下极易触发锁冲突,导致事务重试甚至失败,直接拖垮写入性能;而且一旦事务回滚,新的搜索记录也无法写入,影响可用性。
  • 你当前的分事务思路方向正确,但可以优化:写入事务只做最简单的插入操作,别加查询计数的步骤——查询本身会增加读锁开销,完全没必要。直接在插入后启动独立的删除事务即可,哪怕用户当前记录没超20条,删除语句也不会生效,反而省去了计数的额外开销。

更优的实现方案

1. 调整表结构,从根源提升操作效率

你的主键是search_id,查询用户旧记录时需要全表扫描或额外建索引,效率低且易加锁。建议改成复合主键:

CREATE TABLE user_searches (
    user_id STRING(MAX) NOT NULL,
    occurred_on TIMESTAMP NOT NULL,
    search_id STRING(MAX) NOT NULL,
    search_text STRING(MAX) NOT NULL
) PRIMARY KEY (user_id, occurred_on DESC, search_id);

调整后同一个用户的所有记录会存储在同一个分片内,按时间倒序排列,查询最新/旧记录、执行删除操作的效率会大幅提升,完全不需要额外创建索引。

2. 写入与清理完全解耦(最适配高频场景)

如果业务能接受“短暂超量”(比如用户刚写入第21条记录,过几分钟才被清理),建议直接将写入和清理彻底分开:

  • 写入流程:只执行单条插入操作,不添加任何额外逻辑,保证写入速度最快,完全避免锁冲突。
  • 清理流程:后台运行定时任务(比如每小时一次),使用Spanner的Partitioned DML批量清理超量记录:
PARTITIONED DELETE FROM user_searches
WHERE user_id = @user_id
AND (occurred_on, search_id) NOT IN (
    SELECT occurred_on, search_id
    FROM user_searches
    WHERE user_id = @user_id
    LIMIT 20
)

Partitioned DML是无锁操作,不会影响主流程的读写,百万级数据清理也能轻松处理,最终保证每个用户的记录不超过20条。

3. 实时清理的优化版(若无法接受超量)

要是业务要求绝对不能出现超过20条记录的情况,只能使用单事务,但要优化逻辑以缩短锁持有时间:
在同一个读写事务中,先插入新记录,再直接执行删除语句(跳过前20条,删除剩余记录),无需先查询计数——查询计数纯粹是增加锁开销的多余步骤。使用上述的删除SQL即可,这样事务逻辑更简洁,锁持有时间更短,冲突概率会大幅降低。

最后总结

  • 高频场景优先选择写入+异步批量清理方案,性能拉满且无锁冲突,只要业务能接受最终一致性就用这个。
  • 必须实时限制记录数的话,采用优化后的单事务+复合主键方案,尽量缩短锁持有时间。
  • 避免使用原始分事务(先查计数再删除),查询计数属于增加锁开销的冗余操作。

内容的提问来源于stack exchange,提问作者Gabriel Piffaretti

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 11:10:41