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

Spring Boot集成DynamoDB如何实现主键自增并补全序列空缺值

DynamoDB自增主键补空缺实现方案(Spring Boot版)

DynamoDB本身没有原生的自增主键能力,也没有内置的空缺ID补全逻辑,要实现需求可以通过单独维护序列状态的方案实现,具体步骤如下:


1. 新建序列管理表

先创建一张专门维护ID状态的DynamoDB表,例如命名为sequence_table,表结构如下:

  • 分区键:sequence_name(字符串类型,存储对应业务表的唯一序列标识,比如业务表为事件表,这里可填event_id_seq)
  • 通用属性:
    • current_max_id:数值类型,记录当前已分配过的最大ID
    • used_ids:数值集合类型,记录所有已经被使用的ID

2. 核心实现逻辑

ID分配逻辑遵循「优先补空缺,无空缺则递增」的规则,同时通过乐观锁避免并发场景下的ID重复问题:

  1. 读取序列表中对应业务的current_max_id和used_ids
  2. 遍历1到current_max_id的范围,找到第一个不在used_ids中的数值作为待分配ID
  3. 如果未找到空缺ID,则将current_max_id加1作为新的待分配ID
  4. 将新分配的ID加入used_ids集合,若为新增的最大ID则同步更新current_max_id
  5. 业务数据删除时,需将对应ID从used_ids集合中移除,方便后续分配时补全空缺

3. Spring Boot代码实现

3.1 引入依赖

以AWS Java SDK v2为例,Maven依赖配置如下:

<dependencies>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>dynamodb</artifactId>
        <version>2.20.0</version>
    </dependency>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>enhanced-dynamodb</artifactId>
        <version>2.20.0</version>
    </dependency>
</dependencies>

3.2 序列实体类

import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
import java.util.Set;

@DynamoDbBean
public class Sequence {
    private String sequenceName;
    private Long currentMaxId;
    private Set<Long> usedIds;

    @DynamoDbPartitionKey
    public String getSequenceName() {
        return sequenceName;
    }

    public void setSequenceName(String sequenceName) {
        this.sequenceName = sequenceName;
    }

    public Long getCurrentMaxId() {
        return currentMaxId;
    }

    public void setCurrentMaxId(Long currentMaxId) {
        this.currentMaxId = currentMaxId;
    }

    public Set<Long> getUsedIds() {
        return usedIds;
    }

    public void setUsedIds(Set<Long> usedIds) {
        this.usedIds = usedIds;
    }
}

3.3 ID生成服务

import org.springframework.stereotype.Service;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.Expression;
import software.amazon.awssdk.enhanced.dynamodb.Key;
import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException;
import java.util.HashSet;
import java.util.Set;

@Service
public class IdGeneratorService {
    private static final String BUSINESS_SEQ_NAME = "event_id_seq";
    private final DynamoDbTable<Sequence> sequenceTable;

    public IdGeneratorService(DynamoDbEnhancedClient enhancedClient) {
        this.sequenceTable = enhancedClient.table("sequence_table", 
                software.amazon.awssdk.enhanced.dynamodb.TableSchema.fromBean(Sequence.class));
    }

    public Long generateNextId() {
        int retryTimes = 3;
        while (retryTimes > 0) {
            Sequence currentSeq = sequenceTable.getItem(Key.builder()
                    .partitionValue(BUSINESS_SEQ_NAME)
                    .build());
            // 序列不存在则初始化
            if (currentSeq == null) {
                Sequence initSeq = new Sequence();
                initSeq.setSequenceName(BUSINESS_SEQ_NAME);
                initSeq.setCurrentMaxId(1L);
                initSeq.setUsedIds(Set.of(1L));
                sequenceTable.putItem(initSeq);
                return 1L;
            }

            Long currentMaxId = currentSeq.getCurrentMaxId();
            Set<Long> usedIds = currentSeq.getUsedIds();
            Long nextId = null;

            // 查找最小空缺ID
            for (long i = 1; i <= currentMaxId; i++) {
                if (!usedIds.contains(i)) {
                    nextId = i;
                    break;
                }
            }

            // 无空缺则取最大ID+1
            if (nextId == null) {
                nextId = currentMaxId + 1;
                currentSeq.setCurrentMaxId(nextId);
            }

            // 更新已使用ID集合
            Set<Long> newUsedIds = new HashSet<>(usedIds);
            newUsedIds.add(nextId);
            currentSeq.setUsedIds(newUsedIds);

            try {
                // 乐观锁条件更新,避免并发冲突
                sequenceTable.updateItem(UpdateItemEnhancedRequest.builder(Sequence.class)
                        .item(currentSeq)
                        .conditionExpression(Expression.builder()
                                .expression("currentMaxId = :prevMax")
                                .putExpressionValue(":prevMax", AttributeValue.fromN(currentMaxId.toString()))
                                .build())
                        .build());
                return nextId;
            } catch (ConditionalCheckFailedException e) {
                // 并发冲突重试
                retryTimes--;
            }
        }
        throw new RuntimeException("ID生成失败,并发冲突重试次数耗尽");
    }
}

3.4 数据删除同步逻辑

删除业务数据时需要同步更新序列表的已使用ID集合,方便后续补全空缺:

import org.springframework.stereotype.Service;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.Key;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
import java.util.List;
import java.util.Map;

@Service
public class BusinessDataService {
    private final DynamoDbTable<Event> eventTable;
    private final DynamoDbTable<Sequence> sequenceTable;
    private static final String BUSINESS_SEQ_NAME = "event_id_seq";

    public BusinessDataService(DynamoDbTable<Event> eventTable, DynamoDbTable<Sequence> sequenceTable) {
        this.eventTable = eventTable;
        this.sequenceTable = sequenceTable;
    }

    public void deleteEvent(Long eventId) {
        // 删除业务数据
        eventTable.deleteItem(Key.builder().partitionValue(eventId).build());
        // 从已使用ID集合中移除对应ID
        UpdateItemRequest updateRequest = UpdateItemRequest.builder()
                .tableName("sequence_table")
                .key(Map.of("sequenceName", AttributeValue.fromS(BUSINESS_SEQ_NAME)))
                .updateExpression("DELETE used_ids :removeId")
                .expressionAttributeValues(Map.of(":removeId", AttributeValue.fromNs(List.of(eventId.toString()))))
                .build();
        sequenceTable.updateItem(updateRequest);
    }
}

4. 优化建议

  • 数据量大、空缺ID多的场景下,可以在序列表中新增available_ids集合属性,删除数据时直接把ID加入该集合,分配ID时优先从available_ids取,无需遍历找空缺,性能更高
  • 并发量极高的场景可以适当调大重试次数,或者引入分布式锁进一步降低冲突概率
  • 如果对ID连续性要求不高,建议只做普通自增不补空缺,性能会更好

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 18:54:04