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 类实例,然后将该表传递给描述方法。

示例

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("	%s: %s
", kse.getAttributeName(), kse.getKeyType()); 
   }
   
   Projection projection = lsiDescription.getProjection(); 
   System.out.println("	Projection type: " + projection.getProjectionType()); 
   
   if (projection.getProjectionType().toString().equals("INCLUDE")) { 
      System.out.println("		Non-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 IDE、AWS 凭证文件以及 Eclipse AWS Java 项目中的 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("
*************************************************
"); 
      System.out.println("Executing query on" + tableName);  
      QuerySpec querySpec = new QuerySpec() 
         .withConsistentRead(true) 
         .withScanIndexForward(true) 
         .withReturnConsumedCapacity(ReturnConsumedCapacity.TOTAL);
      
      if (indexName == "OpenStatusIndex") {  
         System.out.println("
Employing index: '" + indexName 
            + "' open orders for this customer.");
            
         System.out.println( 
            "Returns only user-specified attribute list
"); 
         Index index = table.getIndex(indexName); 
             
         querySpec.withKeyConditionExpression("CustomerID = :v_custmid and 
            OpenStatus = :v_openstat") 
            .withValueMap(new ValueMap() 
            .withString(":v_custmid", "jane@sample.com") 
            .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("
Using index: '" + indexName 
            + "': this customer's orders placed after 05/22/2016."); 
         System.out.println("Projected attributes are returned
"); 
         Index index = table.getIndex(indexName); 
             
         querySpec.withKeyConditionExpression("CustomerID = :v_custmid and OrderDate 
            >= :v_ordrdate") 
            .withValueMap(new ValueMap() 
            .withString(":v_custmid", "jane@sample.com") 
            .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("
No index: All Jane's orders by OrderID:
"); 
         querySpec.withKeyConditionExpression("CustomerID = :v_custmid") 
            .withValueMap(new ValueMap()
            .withString(":v_custmid", "jane@example.com"));  
         
         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()); 
         } 
      } 
   } 
}