DynamoDB GSI复合键查询:日期范围+类别查询异常求助
问题背景
作为DynamoDB新手,我有一张用户财务流水表,需要实现按日期范围+特定类别的查询(比如“本月某类别支出金额”)。为此创建了GSI2,其结构为:
- GSI2PK:
USER#${userPhoneNumber} - GSI2SK:
DATE#${date}#CATEGORY#${category}
编写getMovementsByQueryParams函数执行查询时,不传类别能正常返回结果,但传入类别后,查询会返回所有流水数据,而非指定类别+日期范围的数据。我希望实现高效查询,避免客户端过滤数据。
当前使用的KeyConditionExpression:
GS2PK = :gs2pk AND GS2SK BETWEEN :startSk AND :endSk
尝试的ExpressionAttributeValues示例:
{ ":endSk": "DATE#2024-07-11T23:59:00.000Z#CATEGORY#ALIMENTACION~", ":gs2pk": "USER#56963186163", ":startSk": "DATE#2024-07-01T00:00:00.000Z#CATEGORY#ALIMENTACION" }
问题原因
当前GSI2SK的结构是DATE#日期#CATEGORY#类别,字符串排序时日期在前,类别在后。使用BETWEEN条件时,会把所有日期在指定区间内的SK都纳入范围——哪怕类别不同。比如DATE#2024-07-01#CATEGORY#TRANSPORTE的字符串顺序早于DATE#2024-07-11#CATEGORY#ALIMENTACION~,会被误判为符合条件,导致返回所有日期匹配的流水。
解决方案
最优方案:调整GSI2SK的结构
将GSI2SK的字段顺序调换,让类别作为前缀,日期放在后面,新结构为:CATEGORY#${category}#DATE#${date}
调整后,查询的KeyConditionExpression保持不变,对应的ExpressionAttributeValues改为:
{ ":endSk": "CATEGORY#ALIMENTACION#DATE#2024-07-11T23:59:00.000Z~", ":gs2pk": "USER#56963186163", ":startSk": "CATEGORY#ALIMENTACION#DATE#2024-07-01T00:00:00.000Z" }
这样BETWEEN条件会先匹配类别前缀,再限定日期范围,精准过滤出指定类别+日期区间的流水,完全在服务端完成高效查询,无需客户端二次过滤。
临时方案(不修改索引结构)
如果暂时无法修改GSI结构,可以结合FilterExpression过滤类别,但这种方式是先查询所有日期匹配的流水,再在服务端过滤类别,效率低于前者,不推荐用于数据量较大的场景:
KeyConditionExpression: "GS2PK = :gs2pk AND GS2SK BETWEEN :startDateSk AND :endDateSk", FilterExpression: "contains(GS2SK, :category)", ExpressionAttributeValues: { ":gs2pk": "USER#56963186163", ":startDateSk": "DATE#2024-07-01T00:00:00.000Z", ":endDateSk": "DATE#2024-07-11T23:59:00.000Z~", ":category": "#CATEGORY#ALIMENTACION" }
内容的提问来源于stack exchange,提问作者David Diaz

