无状态REST API轮询新增事件记录的可靠方案咨询
嗨,我完全懂你碰到的这个棘手问题——用自增ID当游标看似直观,但因为事务拿ID的顺序和实际提交顺序完全可能不一致,确实会出现漏记录的情况,尤其是高并发场景下这种问题更容易找上门。下面给你几个不需要锁表、不影响性能的可靠解决方案,都是无状态的,完全符合你的需求:
方案一:基于「提交时间戳+ID」的复合游标
这是最通用的方案,核心是按事务实际提交的顺序来筛选记录,而不是ID生成顺序。
首先,你需要确保能获取到事务的提交时间(而不是插入时的时间,因为插入时的now()是事务启动时间,不是提交时间)。以PostgreSQL为例,你可以开启track_commit_timestamp参数(需要在postgresql.conf里设置后重启),然后通过系统函数pg_xact_commit_timestamp(xmin)获取每条记录对应的事务提交时间。
调整你的API参数:让客户端每次请求时带上两个参数after_commit_time和after_id,分别是上次查询返回结果里的最大提交时间和对应的ID。服务器端的查询语句可以写成:
SELECT id, message, timestamp, pg_xact_commit_timestamp(xmin) AS commit_time FROM events WHERE pg_xact_commit_timestamp(xmin) > :after_commit_time OR (pg_xact_commit_timestamp(xmin) = :after_commit_time AND id > :after_id) ORDER BY commit_time, id;
客户端拿到结果后,只需要记录最后一条记录的commit_time和id,作为下一次请求的游标参数即可。
这个方案能保证:即使某个事务先拿了小ID但后提交,它的提交时间会更晚,下次查询一定会被包含进来,不会被遗漏。
方案二:基于数据库事务日志序列号(LSN)的游标
如果你的数据库支持事务日志序列号(比如PostgreSQL的LSN、MySQL的Binlog位置),这是最可靠的方案,因为LSN是严格按照事务提交顺序递增的,完全不会有顺序冲突。
还是以PostgreSQL为例,你可以通过pg_xact_get_lsn(xmin)获取每条记录对应的事务提交LSN。API只需要客户端传递上次返回的最大LSN(比如after_lsn),服务器查询语句:
SELECT id, message, timestamp, pg_xact_get_lsn(xmin) AS commit_lsn FROM events WHERE pg_xact_get_lsn(xmin) > :after_lsn ORDER BY commit_lsn;
客户端每次请求后,记录返回结果里的最大commit_lsn作为下一次的游标参数。
这个方案不需要额外开启参数,LSN本身就是数据库事务提交的唯一有序标识,可靠性拉满,唯一的缺点是不同数据库的LSN实现不一样,迁移起来需要调整。
方案三:避免踩坑的补充建议
- 不要用插入时的
timestamp字段作为游标:因为它是事务启动时的时间,不是提交时间,还是会出现和自增ID一样的顺序问题。 - 不要尝试锁表强制顺序:正如你担心的,这会彻底毁掉并发性能,完全没必要。
- 如果用复合游标,确保时间戳的精度足够:比如用
timestamptz到微秒级别,减少同一时间戳下的ID冲突概率。
内容来源于stack exchange

