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

SpringBoot集成DynamoDB:高效删除指定日期前数据的方案咨询

解决DynamoDB按creation_date批量删除数据的高效方案

问题背景

我在开发Java SpringBoot集成DynamoDB的项目时,需要删除所有creation_date(存储为秒级时间戳)小于指定日期的数据。当前只能用效率极低的DynamoDBScanExpression实现扫描删除,而尝试用Query构建条件时无法添加ComparisonOperator.LE——因为现有全局二级索引(GSI)的哈希键是creation_date,不支持范围查询。允许修改表结构,但必须保留主索引为哈希键my_id+范围键my_type。

当前表结构创建命令

aws dynamodb create-table  \
    --table-name my-table \
    --attribute-definitions \
        AttributeName=my_id,AttributeType=S \
        AttributeName=my_type,AttributeType=S \
        AttributeName=creation_date,AttributeType=N \
    --key-schema \
        AttributeName=my_id,KeyType=HASH \
        AttributeName=my_type,KeyType=RANGE \
    --billing-mode PAY_PER_REQUEST \
    --endpoint-url $ENDPOINT:$PORT \
    --global-secondary-indexes \
        "[ \
            { \
                \"IndexName\": \"CreationDateIndex\", \
                \"KeySchema\": [{\"AttributeName\":\"creation_date\",\"KeyType\":\"HASH\"}], \
                \"Projection\":{ \
                    \"ProjectionType\":\"ALL\" \
                }, \
                \"ProvisionedThroughput\": { \
                    \"ReadCapacityUnits\": 10, \
                    \"WriteCapacityUnits\": 5 \
                } \
            } \
        ]"

当前实体类定义

@NotNull
@DynamoDBHashKey(attributeName = "my_id")
protected UUID myId;

@NotNull
@DynamoDBRangeKey(attributeName = "my_type")
protected String myType;

@DynamoDBIndexHashKey(globalSecondaryIndexName = "CreationDateIndex")
@DynamoDBAttribute(attributeName = "creation_date")
protected long creationDate;

当前低效实现代码

@Override
public boolean delete(long creationDateBefore) throws InterruptedException {
    System.out.println("STARTING DELETING objects");
    List<MyEntity> objectsToDelete = findByCreationDateBefore(creationDateBefore);
    System.out.println("DELETING " + objectsToDelete.size() + " objects");
    return batchDelete(objectsToDelete).isEmpty();
}

private List<MyEntity> findByCreationDateBefore(long creationDateBefore) {
    DynamoDBScanExpression scanExpression = new DynamoDBScanExpression();
    scanExpression.addFilterCondition(
            "creation_date",
            new Condition()
                    .withComparisonOperator(ComparisonOperator.LE)
                    .withAttributeValueList(new AttributeValue().withN(String.valueOf(creationDateBefore)))
    );

    return dbMapper.scan(MyEntity.class, scanExpression);
}

可行解决方案

方案1:重构GSI结构实现高效Query查询

当前GSI把creation_date作为哈希键,仅支持等值查询,无法做范围过滤。我们可以修改GSI:用一个固定值的哈希键(比如"ALL"),将creation_date设为GSI的范围键,这样就能通过Query高效筛选符合条件的数据。

步骤1:更新表GSI结构

aws dynamodb update-table \
    --table-name my-table \
    --attribute-definitions AttributeName=fixed_partition,AttributeType=S \
    --global-secondary-index-updates "[{
        \"Update\": {
            \"IndexName\": \"CreationDateIndex\",
            \"KeySchema\": [{\"AttributeName\":\"fixed_partition\",\"KeyType\":\"HASH\"}, {\"AttributeName\":\"creation_date\",\"KeyType\":\"RANGE\"}],
            \"Projection\": {\"ProjectionType\":\"ALL\"},
            \"ProvisionedThroughput\": {\"ReadCapacityUnits\":10, \"WriteCapacityUnits\":5}
        }
    }]"

步骤2:更新实体类

新增固定哈希键字段,调整GSI注解:

@NotNull
@DynamoDBHashKey(attributeName = "my_id")
protected UUID myId;

@NotNull
@DynamoDBRangeKey(attributeName = "my_type")
protected String myType;

@DynamoDBAttribute(attributeName = "creation_date")
@DynamoDBIndexRangeKey(globalSecondaryIndexName = "CreationDateIndex")
protected long creationDate;

// GSI固定哈希键,所有数据统一设为"ALL"
@DynamoDBIndexHashKey(globalSecondaryIndexName = "CreationDateIndex")
@DynamoDBAttribute(attributeName = "fixed_partition")
protected String fixedPartition = "ALL";

步骤3:用Query替代Scan查询

private List<MyEntity> findByCreationDateBefore(long creationDateBefore) {
    MyEntity hashKeyTemplate = new MyEntity();
    hashKeyTemplate.setFixedPartition("ALL");

    DynamoDBQueryExpression<MyEntity> queryExpr = new DynamoDBQueryExpression<MyEntity>()
            .withHashKeyValues(hashKeyTemplate)
            .withIndexName("CreationDateIndex")
            .withRangeKeyCondition("creation_date",
                new Condition()
                    .withComparisonOperator(ComparisonOperator.LE)
                    .withAttributeValueList(new AttributeValue().withN(String.valueOf(creationDateBefore))))
            .withConsistentRead(false); // GSI不支持强一致读,必须设为false

    return dbMapper.query(MyEntity.class, queryExpr);
}

方案2:使用PartiQL批量操作(中小数据量适用)

通过PartiQL先查询符合条件的主键,再批量删除,效率比Scan更高,需注意单次批量删除最多25条的限制,要做分页处理。

示例代码

private List<MyEntity> findByCreationDateBeforeWithPartiQL(long creationDateBefore) {
    String querySql = "SELECT my_id, my_type FROM \"my-table\" WHERE creation_date <= ?";
    List<AttributeValue> params = Collections.singletonList(new AttributeValue().withN(String.valueOf(creationDateBefore)));
    
    List<MyEntity> result = new ArrayList<>();
    PaginatedQueryList<Map<String, AttributeValue>> queryResult = dbMapper.query(Map.class, querySql, params);
    
    for (Map<String, AttributeValue> item : queryResult) {
        MyEntity entity = new MyEntity();
        entity.setMyId(UUID.fromString(item.get("my_id").getS()));
        entity.setMyType(item.get("my_type").getS());
        result.add(entity);
    }
    return result;
}

方案3:异步自动清理(定期清理场景适用)

如果是定期清理旧数据的需求,可采用以下两种方式:

  • DynamoDB TTL:将creation_date设置为数据的过期时间戳(或基于创建时间计算出过期时间),开启TTL功能后,DynamoDB会自动异步删除过期数据,无需手动编写删除逻辑。
  • Streams+Lambda:给表开启DynamoDB Streams,用Lambda函数定期通过方案1的GSI查询旧数据,批量删除。

注意:TTL功能是异步执行,数据不会立即被删除,适合对删除时效性要求不高的场景。

总结

优先选择方案1,通过重构GSI将creation_date设为范围键,用Query替代Scan,这是效率最高的方案。如果无法修改表结构,方案2的PartiQL查询比Scan更高效,但需注意分页和批量操作限制。方案3适合定期清理场景,无需手动维护删除逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 03:00:59