DynamoDB – 本地二级索引
DynamoDB – 本地二级索引
某些应用程序仅使用主键执行查询,但某些情况下会受益于备用排序键。通过创建单个或多个本地二级索引,允许您的应用程序进行选择。
复杂的数据访问要求,例如组合数百万个项目,需要执行更高效的查询/扫描。本地二级索引为分区键值提供备用排序键。它们还保存所有或某些表属性的副本。它们按表分区键组织数据,但使用不同的排序键。
使用本地二级索引消除了对整个表扫描的需要,并允许使用排序键进行简单快速的查询。
所有本地二级索引必须满足某些条件 –
- 相同的分区键和源表分区键。
- 只有一个标量属性的排序键。
- 源表排序键作为非键属性的投影。
所有本地二级索引都会自动保存父表中的分区和排序键。在查询中,这意味着有效收集投影属性,以及检索未投影的属性。
本地二级索引的存储限制仍然是每个分区键值 10GB,其中包括所有表项和共享分区键值的索引项。
投影属性
由于复杂性,某些操作需要额外的读取/获取。这些操作会消耗大量吞吐量。投影允许您通过隔离这些属性来避免代价高昂的获取和执行丰富的查询。请记住,投影由复制到二级索引中的属性组成。
在制作二级索引时,您指定投影的属性。回想一下 DynamoDB 提供的三个选项:KEYS_ONLY、INCLUDE 和 ALL。
在投影中选择某些属性时,请考虑相关的成本权衡 –
-
如果您只投影一小组必要的属性,则可以显着降低存储成本。
-
如果您投影经常访问的非关键属性,您可以用存储成本抵消扫描成本。
-
如果您投影大部分或所有非关键属性,这将最大限度地提高灵活性并降低吞吐量(无检索);然而,存储成本上升。
-
如果您将 KEYS_ONLY 用于频繁的写入/更新和不频繁的查询,它会最小化大小,但会保持查询准备。
本地二级索引创建
使用CreateTable的LocalSecondaryIndex参数创建单个或多个本地二级索引。您必须为排序键指定一个非键属性。在创建表时,您创建本地二级索引。删除时,您将删除这些索引。
具有本地二级索引的表必须遵守每个分区键值 10GB 的大小限制,但可以存储任意数量的项目。
本地二级索引查询和扫描
当索引中的多个项目共享排序键值时,对本地二级索引的查询操作将返回具有匹配分区键值的所有项目。匹配项不会按特定顺序返回。对本地二级索引的查询使用最终一致性或强一致性,强一致性读取提供最新值。
扫描操作返回所有本地二级索引数据。扫描要求您提供表和索引名称,并允许使用过滤器表达式来丢弃数据。
项目写作
创建本地二级索引时,指定排序键属性及其数据类型。编写项目时,如果项目定义了索引键的属性,则其类型必须与键模式的数据类型相匹配。
DynamoDB 对表项和本地二级索引项没有任何一对一的关系要求。具有多个本地二级索引的表比那些较少的表具有更高的写入成本。
本地二级索引中的吞吐量注意事项
查询的读取容量消耗取决于数据访问的性质。查询使用最终一致性或强一致性,强一致性读取使用一个单元,而最终一致性读取使用半个单元。
结果限制包括最大 1MB 大小。结果大小来自四舍五入到最接近的 4KB 的匹配索引项大小的总和,并且匹配表项大小也四舍五入到最接近的 4KB。
写入容量消耗保持在预配单位内。通过查找表写入中消耗的单位和更新索引中消耗的单位的总和来计算总配置成本。
您还可以考虑影响成本的关键因素,其中一些可以是 –
-
当您编写定义索引属性的项目或更新项目以定义未定义的索引属性时,会发生单个写入操作。
-
当表更新更改索引键属性值时,会发生两次写入以删除然后添加一个项目。
-
当写入导致删除索引属性时,会发生一次写入以删除旧项目投影。
-
如果在更新之前或之后索引中不存在某个项目,则不会发生写入。
本地二级索引存储
在表项写入时,DynamoDB 会自动将正确的属性集复制到所需的本地二级索引。这会向您的帐户收费。使用的空间由表主键字节大小、索引键属性字节大小、任何当前预计的属性字节大小以及每个索引项的 100 字节开销的总和得出。
估计存储量是通过估计平均索引项大小并乘以表项数量得到的。
使用 Java 处理本地二级索引
通过首先创建 DynamoDB 类实例来创建本地二级索引。然后,使用必要的请求信息创建一个 CreateTableRequest 类实例。最后,使用 createTable 方法。
例子
DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient( new ProfileCredentialsProvider())); String tableName = "Tools"; CreateTableRequest createTableRequest = new CreateTableRequest().withTableName(tableName); //Provisioned Throughput createTableRequest.setProvisionedThroughput ( new ProvisionedThroughput() .withReadCapacityUnits((long)5) .withWriteCapacityUnits(( long)5)); //Attributes ArrayList<AttributeDefinition> attributeDefinitions = new ArrayList<AttributeDefinition>(); attributeDefinitions.add(new AttributeDefinition() .withAttributeName("Make") .withAttributeType("S")); attributeDefinitions.add(new AttributeDefinition() .withAttributeName("Model") .withAttributeType("S")); attributeDefinitions.add(new AttributeDefinition() .withAttributeName("Line") .withAttributeType("S")); createTableRequest.setAttributeDefinitions(attributeDefinitions); //Key Schema ArrayList<KeySchemaElement> tableKeySchema = new ArrayList<KeySchemaElement>(); tableKeySchema.add(new KeySchemaElement() .withAttributeName("Make") .withKeyType(KeyType.HASH)); //Partition key tableKeySchema.add(new KeySchemaElement() .withAttributeName("Model") .withKeyType(KeyType.RANGE)); //Sort key createTableRequest.setKeySchema(tableKeySchema); ArrayList<KeySchemaElement> indexKeySchema = new ArrayList<KeySchemaElement>(); indexKeySchema.add(new KeySchemaElement() .withAttributeName("Make") .withKeyType(KeyType.HASH)); //Partition key indexKeySchema.add(new KeySchemaElement() .withAttributeName("Line") .withKeyType(KeyType.RANGE)); //Sort key Projection projection = new Projection() .withProjectionType(ProjectionType.INCLUDE); ArrayList<String> nonKeyAttributes = new ArrayList<String>(); nonKeyAttributes.add("Type"); nonKeyAttributes.add("Year"); projection.setNonKeyAttributes(nonKeyAttributes); LocalSecondaryIndex localSecondaryIndex = new LocalSecondaryIndex() .withIndexName("ModelIndex") .withKeySchema(indexKeySchema) .withProjection(p rojection); ArrayList<LocalSecondaryIndex> localSecondaryIndexes = new ArrayList<LocalSecondaryIndex>(); localSecondaryIndexes.add(localSecondaryIndex); createTableRequest.setLocalSecondaryIndexes(localSecondaryIndexes); Table table = dynamoDB.createTable(createTableRequest); System.out.println(table.getDescription());
使用 describe 方法检索有关本地二级索引的信息。只需创建一个 DynamoDB 类实例,创建一个 Table 类实例,然后将表传递给 describe 方法。
例子
DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient( new ProfileCredentialsProvider())); String tableName = "Tools"; Table table = dynamoDB.getTable(tableName); TableDescription tableDescription = table.describe(); List<LocalSecondaryIndexDescription> localSecondaryIndexes = tableDescription.getLocalSecondaryIndexes(); Iterator<LocalSecondaryIndexDescription> lsiIter = localSecondaryIndexes.iterator(); while (lsiIter.hasNext()) { LocalSecondaryIndexDescription lsiDescription = lsiIter.next(); System.out.println("Index info " + lsiDescription.getIndexName() + ":"); Iterator<KeySchemaElement> kseIter = lsiDescription.getKeySchema().iterator(); while (kseIter.hasNext()) { KeySchemaElement kse = kseIter.next(); System.out.printf("\t%s: %s\n", kse.getAttributeName(), kse.getKeyType()); } Projection projection = lsiDescription.getProjection(); System.out.println("\tProjection type: " + projection.getProjectionType()); if (projection.getProjectionType().toString().equals("INCLUDE")) { System.out.println("\t\tNon-key projected attributes: " + projection.getNonKeyAttributes()); } }
使用与表查询相同的步骤执行查询。只需创建一个 DynamoDB 类实例、一个 Table 类实例、一个 Index 类实例、一个查询对象,并使用查询方法。
例子
DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient( new ProfileCredentialsProvider())); String tableName = "Tools"; Table table = dynamoDB.getTable(tableName); Index index = table.getIndex("LineIndex"); QuerySpec spec = new QuerySpec() .withKeyConditionExpression("Make = :v_make and Line = :v_line") .withValueMap(new ValueMap() .withString(":v_make", "Depault") .withString(":v_line", "SuperSawz")); ItemCollection<QueryOutcome> items = index.query(spec); Iterator<Item> itemsIter = items.iterator(); while (itemsIter.hasNext()) { Item item = itemsIter.next(); System.out.println(item.toJSONPretty()); }
您还可以查看以下示例。
注意– 以下示例可能假设先前创建的数据源。在尝试执行之前,获取支持库并创建必要的数据源(具有所需特征的表或其他参考源)。
以下示例还在 Eclipse AWS Java 项目中使用 Eclipse IDE、AWS 凭证文件和 AWS Toolkit。
例子
import java.util.ArrayList; import java.util.Iterator; import com.amazonaws.auth.profile.ProfileCredentialsProvider; import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; import com.amazonaws.services.dynamodbv2.document.DynamoDB; import com.amazonaws.services.dynamodbv2.document.Index; import com.amazonaws.services.dynamodbv2.document.Item; import com.amazonaws.services.dynamodbv2.document.ItemCollection; import com.amazonaws.services.dynamodbv2.document.PutItemOutcome; import com.amazonaws.services.dynamodbv2.document.QueryOutcome; import com.amazonaws.services.dynamodbv2.document.Table; import com.amazonaws.services.dynamodbv2.document.spec.QuerySpec; import com.amazonaws.services.dynamodbv2.document.utils.ValueMap; import com.amazonaws.services.dynamodbv2.model.AttributeDefinition; import com.amazonaws.services.dynamodbv2.model.CreateTableRequest; import com.amazonaws.services.dynamodbv2.model.KeySchemaElement; import com.amazonaws.services.dynamodbv2.model.KeyType; import com.amazonaws.services.dynamodbv2.model.LocalSecondaryIndex; import com.amazonaws.services.dynamodbv2.model.Projection; import com.amazonaws.services.dynamodbv2.model.ProjectionType; import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput; import com.amazonaws.services.dynamodbv2.model.ReturnConsumedCapacity; import com.amazonaws.services.dynamodbv2.model.Select; public class LocalSecondaryIndexSample { static DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient( new ProfileCredentialsProvider())); public static String tableName = "ProductOrders"; public static void main(String[] args) throws Exception { createTable(); query(null); query("IsOpenIndex"); query("OrderCreationDateIndex"); } public static void createTable() { CreateTableRequest createTableRequest = new CreateTableRequest() .withTableName(tableName) .withProvisionedThroughput(new ProvisionedThroughput() .withReadCapacityUnits((long) 1) .withWriteCapacityUnits((long) 1)); // Table partition and sort keys attributes ArrayList<AttributeDefinition> attributeDefinitions = new ArrayList<AttributeDefinition>(); attributeDefinitions.add(new AttributeDefinition() .withAttributeName("CustomerID") .withAttributeType("S")); attributeDefinitions.add(new AttributeDefinition() .withAttributeName("OrderID") .withAttributeType("N")); // Index primary key attributes attributeDefinitions.add(new AttributeDefinition() .withAttributeName("OrderDate") .withAttributeType("N")); attributeDefinitions.add(new AttributeDefinition() .withAttributeName("OpenStatus") .withAttributeType("N")); createTableRequest.setAttributeDefinitions(attributeDefinitions); // Table key schema ArrayList<KeySchemaElement> tableKeySchema = new ArrayList<KeySchemaElement>(); tableKeySchema.add(new KeySchemaElement() .withAttributeName("CustomerID") .withKeyType(KeyType.HASH)); //Partition key tableKeySchema.add(new KeySchemaElement() .withAttributeName("OrderID") .withKeyType(KeyType.RANGE)); //Sort key createTableRequest.setKeySchema(tableKeySchema); ArrayList<LocalSecondaryIndex> localSecondaryIndexes = new ArrayList<LocalSecondaryIndex>(); // OrderDateIndex LocalSecondaryIndex orderDateIndex = new LocalSecondaryIndex() .withIndexName("OrderDateIndex"); // OrderDateIndex key schema ArrayList<KeySchemaElement> indexKeySchema = new ArrayList<KeySchemaElement>(); indexKeySchema.add(new KeySchemaElement() .withAttributeName("CustomerID") .withKeyType(KeyType.HASH)); //Partition key indexKeySchema.add(new KeySchemaElement() .withAttributeName("OrderDate") .withKeyType(KeyType.RANGE)); //Sort key orderDateIndex.setKeySchema(indexKeySchema); // OrderCreationDateIndex projection w/attributes list Projection projection = new Projection() .withProjectionType(ProjectionType.INCLUDE); ArrayList<String> nonKeyAttributes = new ArrayList<String>(); nonKeyAttributes.add("ProdCat"); nonKeyAttributes.add("ProdNomenclature"); projection.setNonKeyAttributes(nonKeyAttributes); orderCreationDateIndex.setProjection(projection); localSecondaryIndexes.add(orderDateIndex); // IsOpenIndex LocalSecondaryIndex isOpenIndex = new LocalSecondaryIndex() .withIndexName("IsOpenIndex"); // OpenStatusIndex key schema indexKeySchema = new ArrayList<KeySchemaElement>(); indexKeySchema.add(new KeySchemaElement() .withAttributeName("CustomerID") .withKeyType(KeyType.HASH)); //Partition key indexKeySchema.add(new KeySchemaElement() .withAttributeName("OpenStatus") .withKeyType(KeyType.RANGE)); //Sort key // OpenStatusIndex projection projection = new Projection() .withProjectionType(ProjectionType.ALL); OpenStatusIndex.setKeySchema(indexKeySchema); OpenStatusIndex.setProjection(projection); localSecondaryIndexes.add(OpenStatusIndex); // Put definitions in CreateTable request createTableRequest.setLocalSecondaryIndexes(localSecondaryIndexes); System.out.println("Spawning table " + tableName + "..."); System.out.println(dynamoDB.createTable(createTableRequest)); // Pause for ACTIVE status System.out.println("Waiting for ACTIVE table:" + tableName); try { Table table = dynamoDB.getTable(tableName); table.waitForActive(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void query(String indexName) { Table table = dynamoDB.getTable(tableName); System.out.println("\n*************************************************\n"); System.out.println("Executing query on" + tableName); QuerySpec querySpec = new QuerySpec() .withConsistentRead(true) .withScanIndexForward(true) .withReturnConsumedCapacity(ReturnConsumedCapacity.TOTAL); if (indexName == "OpenStatusIndex") { System.out.println("\nEmploying index: '" + indexName + "' open orders for this customer."); System.out.println( "Returns only user-specified attribute list\n"); Index index = table.getIndex(indexName); querySpec.withKeyConditionExpression("CustomerID = :v_custmid and OpenStatus = :v_openstat") .withValueMap(new ValueMap() .withString(":v_custmid", "[email protected]") .withNumber(":v_openstat", 1)); querySpec.withProjectionExpression( "OrderDate, ProdCat, ProdNomenclature, OrderStatus"); ItemCollection<QueryOutcome> items = index.query(querySpec); Iterator<Item> iterator = items.iterator(); System.out.println("Printing query results..."); while (iterator.hasNext()) { System.out.println(iterator.next().toJSONPretty()); } } else if (indexName == "OrderDateIndex") { System.out.println("\nUsing index: '" + indexName + "': this customer's orders placed after 05/22/2016."); System.out.println("Projected attributes are returned\n"); Index index = table.getIndex(indexName); querySpec.withKeyConditionExpression("CustomerID = :v_custmid and OrderDate >= :v_ordrdate") .withValueMap(new ValueMap() .withString(":v_custmid", "[email protected]") .withNumber(":v_ordrdate", 20160522)); querySpec.withSelect(Select.ALL_PROJECTED_ATTRIBUTES); ItemCollection<QueryOutcome> items = index.query(querySpec); Iterator<Item> iterator = items.iterator(); System.out.println("Printing query results..."); while (iterator.hasNext()) { System.out.println(iterator.next().toJSONPretty()); } } else { System.out.println("\nNo index: All Jane's orders by OrderID:\n"); querySpec.withKeyConditionExpression("CustomerID = :v_custmid") .withValueMap(new ValueMap() .withString(":v_custmid", "[email protected]")); ItemCollection<QueryOutcome> items = table.query(querySpec); Iterator<Item> iterator = items.iterator(); System.out.println("Printing query results..."); while (iterator.hasNext()) { System.out.println(iterator.next().toJSONPretty()); } } } }