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

使用DynamoDbIndex.scan()带过滤表达式无结果问题排查

问题描述

我使用如下Java实体类对应DynamoDB表:

@DynamoDbBean
@Data
public class RecordDynamoDB {
    private String id;
    private String customerIdHash;
    private OffsetDateTime expirationTime;

    @DynamoDbPartitionKey
    public String getId() {
        return id;
    }

    @DynamoDbSecondarySortKey(indexNames = {"expirationTime-index"})
    @DynamoDbConvertedBy(DynamoDBTimestampConverter.class)
    public OffsetDateTime getExpirationTime() {
        return expirationTime;
    }

    @DynamoDbSecondaryPartitionKey(indexNames = "customerIdHash-index")
    public String getCustomerIdHash() {
        return customerIdHash;
    }
}

原扫描代码可正常查询符合指定状态和过期时间的记录:

var attributeValueMap = Map.of(
        ":status", AttributeValue.fromS(Status.EXPIRED.getValue()),
        ":expirationTime", DATE_TIME_CONVERTER.transformFrom(now)
);

var scanRequest = ScanRequest.builder()
        .tableName(TABLE_NAME)
        .filterExpression("expirationTime < :expirationTime AND grant.#status = :status")
        .expressionAttributeValues(attributeValueMap)
        .expressionAttributeNames(Map.of("#status", "status"))
        .build();

return dynamoDbClient.scan(scanRequest)
        .items()
        .stream()
        .map(recordTableSchema::mapToItem)
        .toList();

为提升查询性能,我创建了名为expirationTime-index的GSI,以customerIdHash为分区键、expirationTime为排序键,改写代码使用该索引扫描并携带过滤表达式,但未返回任何记录。即使更新customerIdHash的注解指定该GSI后问题仍存在,且索引扫描返回空页,而表本身有48条记录,请问问题出在哪里?

问题原因分析
  • GSI注解配置不匹配:实体类中customerIdHash的@DynamoDbSecondaryPartitionKey指定的索引是customerIdHash-index,而expirationTime的@DynamoDbSecondarySortKey指定的是expirationTime-index,两个字段未关联到同一个GSI,导致你创建的expirationTime-index无法正确映射实体类的键结构。
  • GSI未投影过滤所需属性:DynamoDB的GSI仅包含主键属性(分区+排序)和显式配置的投影属性。你的过滤条件用到了status字段,但如果GSI没有投影该字段,扫描时无法读取到status值,自然会过滤掉所有结果。
  • GSI数据未同步:如果GSI是新创建的,或者之前注解配置错误导致数据未同步到GSI,索引中会没有数据,扫描返回空页。
解决方案
  1. 修正GSI实体类注解
    将customerIdHash的注解索引名称改为expirationTime-index,确保两个字段属于同一个GSI:

    @DynamoDbSecondaryPartitionKey(indexNames = "expirationTime-index")
    public String getCustomerIdHash() {
        return customerIdHash;
    }
    
    @DynamoDbSecondarySortKey(indexNames = {"expirationTime-index"})
    @DynamoDbConvertedBy(DynamoDBTimestampConverter.class)
    public OffsetDateTime getExpirationTime() {
        return expirationTime;
    }
    

    同时确保DynamoDB控制台或基础设施代码中,expirationTime-index的分区键为customerIdHash、排序键为expirationTime。

  2. 配置GSI投影属性
    确保expirationTime-index投影了status字段:

    • 控制台创建/修改GSI时,将投影类型设置为ALL,或显式添加status到投影属性列表;
    • 若使用注解驱动,可添加@DynamoDbIndexProjection指定投影属性。
  3. 验证GSI数据同步
    等待GSI状态变为ACTIVE(可通过DynamoDB控制台查看),确认现有数据已同步到索引中。

  4. 修正扫描请求
    扫描时明确指定索引名称:

    var attributeValueMap = Map.of(
            ":status", AttributeValue.fromS(Status.EXPIRED.getValue()),
            ":expirationTime", DATE_TIME_CONVERTER.transformFrom(now)
    );
    
    var scanRequest = ScanRequest.builder()
            .tableName(TABLE_NAME)
            .indexName("expirationTime-index") // 指定目标GSI
            .filterExpression("expirationTime < :expirationTime AND #status = :status")
            .expressionAttributeValues(attributeValueMap)
            .expressionAttributeNames(Map.of("#status", "status"))
            .build();
    
    return dynamoDbClient.scan(scanRequest)
            .items()
            .stream()
            .map(recordTableSchema::mapToItem)
            .toList();
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 15:30:58