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

多服务器环境下原子性获取并失效未使用ID的数据库方案

预生成未使用ID的原子性分配方案(多服务器场景)

你的核心需求是原子性获取并标记/删除未使用ID,避免多服务器并发下的重复分配,这是分布式系统中常见的资源分配场景,完全可以通过原子操作实现,也是行业内的常规做法。以下分SQL数据库和DynamoDB两种场景给出具体方案:

一、SQL数据库方案(低频率场景首选)

由于你的API使用频率不高,SQL数据库的事务与锁机制足够应对,且实现简单、易于维护。

1. 表结构优化

不要使用重复字段存储ID,建议创建一张结构清晰的表:

CREATE TABLE unused_ids (
    id VARCHAR(255) PRIMARY KEY, -- 存储预生成的未使用ID
    is_used BOOLEAN DEFAULT FALSE -- 标记是否已被使用
);

2. 原子获取并标记操作

使用SELECT ... FOR UPDATE SKIP LOCKED(MySQL 8.0+、PostgreSQL 9.5+支持),这是行业标准的行级锁方案,既能保证原子性,又能避免请求阻塞:

BEGIN TRANSACTION;
-- 原子锁定一个未使用ID,跳过已被其他事务锁定的行
SELECT id FROM unused_ids WHERE is_used = FALSE LIMIT 1 FOR UPDATE SKIP LOCKED;
-- 将该ID标记为已用
UPDATE unused_ids SET is_used = TRUE WHERE id = '<获取到的ID>';
COMMIT;

如果不需要保留历史记录,也可以直接用原子删除操作:

-- 删除并返回第一个未使用ID,全程原子性执行
DELETE FROM unused_ids WHERE id = (SELECT id FROM unused_ids LIMIT 1) RETURNING id;

二、DynamoDB方案(无需迁移时的实现)

DynamoDB通过条件表达式支持原子操作,可实现“获取+标记/删除”的原子性要求:

1. 表结构调整

将每个未使用ID作为单独Item,主键设为id,新增status字段标记状态(unused/used):

{
  "id": "id1",
  "status": "unused"
}

2. 原子获取并标记/删除

方式1:原子标记为已用

通过UpdateItem结合条件表达式,确保只有当ID处于未使用状态时才会被标记,同时返回原数据:

import boto3

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('unused_ids')

# 先扫描获取一个未使用ID(低频率下扫描开销可忽略)
scan_resp = table.scan(
    FilterExpression='status = :unused',
    ExpressionAttributeValues={':unused': 'unused'},
    Limit=1
)

if scan_resp['Items']:
    target_id = scan_resp['Items'][0]['id']
    try:
        # 原子更新状态,条件不满足时抛出异常
        update_resp = table.update_item(
            Key={'id': target_id},
            UpdateExpression='SET status = :used',
            ConditionExpression='status = :unused',
            ExpressionAttributeValues={':used': 'used', ':unused': 'unused'},
            ReturnValues='ALL_OLD'
        )
        # 成功获取到可用ID:update_resp['Attributes']['id']
    except table.meta.client.exceptions.ConditionalCheckFailedException:
        # 该ID已被其他请求占用,重新扫描下一个即可
        pass

方式2:原子删除

直接删除未使用ID,同样通过条件表达式保证原子性:

try:
    delete_resp = table.delete_item(
        Key={'id': target_id},
        ConditionExpression='status = :unused',
        ExpressionAttributeValues={':unused': 'unused'},
        ReturnValues='ALL_OLD'
    )
    # 成功获取到可用ID:delete_resp['Attributes']['id']
except table.meta.client.exceptions.ConditionalCheckFailedException:
    # 重试逻辑
    pass

三、行业最佳实践

  • 原子操作是核心:无论使用哪种数据库,必须依赖数据库原生的原子操作实现“获取+标记/删除”,避免在应用层做“先查询再更新”的非原子逻辑,否则会出现竞态条件导致重复分配。
  • 多服务器场景无需额外全局锁:不要引入Redis等第三方分布式锁增加复杂度,直接依赖数据库的原子性机制即可,更可靠且易于维护。
  • 低频率场景优先选SQL:SQL的事务与锁机制更成熟,实现成本更低,还便于后续的ID使用情况回溯。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 22:15:34