在不使用分区键的情况下查询DynamoDB表的全局二级索引。
在不使用分区键的情况下查询DynamoDB表的全局二级索引。
我的DynamoDB表中的分区键是userID
,没有排序键。表中每个项目还有一个timestamp
属性。我想要检索所有在指定范围内的具有时间戳的项目(不考虑userID
,即跨越所有分区的范围)。\n在阅读文档和搜索Stack Overflow(这里)之后,我发现我需要为我的表创建一个GSI。\n因此,我创建了一个具有以下键的GSI:\n
- \n
- 分区键:
userID
- 排序键:
timestamp
\n
\n
\n我正在使用以下代码使用Java SDK查询索引:\n
String lastWeekDateString = getLastWeekDateString(); AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build(); DynamoDB dynamoDB = new DynamoDB(client); Table table = dynamoDB.getTable("user table"); Index index = table.getIndex("userID-timestamp-index"); QuerySpec querySpec = new QuerySpec() .withKeyConditionExpression("timestamp > :v_timestampLowerBound") .withValueMap(new ValueMap() .withString(":v_timestampLowerBound", lastWeekDateString)); ItemCollectionitems = index.query(querySpec); Iterator - iter = items.iterator(); while (iter.hasNext()) { Item item = iter.next(); // 在此处提取项目属性 }
\n执行此代码时,我遇到了以下错误:\n
查询条件缺少键模式元素:userID
\n根据我所知,我应该能够只使用排序键查询GSI,而不需要在分区键上给出任何条件。请帮我理解我的实现有什么问题。谢谢。\n编辑:在阅读这里的主题后,结果显示我们无法仅通过排序键对GSI进行查询。那么,如果有的话,如何通过属性的范围查询整个表的替代方法?我在那个主题中找到的一个建议是使用年份作为分区键。如果所需范围跨越多年,这将需要多个查询。此外,这不会均匀地将数据分布在所有分区中,因为只有与当前年份对应的分区将用于整整一年的插入。请提供任何替代方案的建议。
问题的出现原因:在使用DynamoDB的全局二级索引(GSI)查询时,如果不使用分区键,会导致查询效率低下,需要进行过滤或扫描操作。
解决方法:通过正确设计主键方案,可以解决自定义查询需求。在设计DynamoDB的主键时,主要原则是哈希键(hash key)用于对整个项目进行分区,排序键(sort key)用于对分区内的项目进行排序。推荐将时间戳的年份作为哈希键,月份和日期作为排序键。在这种情况下,最多只需要进行2次查询。
示例代码如下:
如果开始日期和结束日期的年份相同,只需要进行一次查询:
.withKeyConditionExpression("#year = :year and #month-date > :start-month-date and #month-date < :end-month-date")
如果开始日期和结束日期的年份不同,需要进行两次查询:
.withKeyConditionExpression("#year = :start-year and #month-date > :start-month-date")
.withKeyConditionExpression("#year = :end-year and #month-date < :end-month-date")
最后,将两次查询的结果集进行合并。这样最多只消耗2个读取容量单位。为了更好地比较排序键,可能需要使用UNIX时间戳。
感谢阅读!
在使用DynamoDB的查询操作时,必须至少指定分区键。这就是为什么会出现"userId"是必需的错误。(在AWS Query文档中)
唯一的方法来获取没有分区键的项是执行扫描操作(但这不会按照排序键进行排序!)。
如果您想获取所有已排序的项,您需要创建一个具有分区键的全局二级索引(GSI),该分区键对于您需要的所有项都是相同的(例如,在所有项上创建一个新属性,如"type": "item")。然后,您可以查询GSI并指定#type=:item。
代码示例:
QuerySpec querySpec = new QuerySpec() .withKeyConditionExpression(":type = #item AND timestamp > :v_timestampLowerBound") .withKeyMap(new KeyMap() .withString("#type", "type")) .withValueMap(new ValueMap() .withString(":v_timestampLowerBound", lastWeekDateString) .withString(":item", "item"));
将"type"作为分区键,并为所有项中的"type"设置相同的值的问题在于读取负载集中在一个分区上,这破坏了DynamoDB的可扩展性。我不想执行扫描操作,因为这会读取所有项并增加很多成本。