AWS DynamoDB 常用操作

在上篇blog 简单介绍了 DynamoDB 初步使用。但DynamoDb实际上使用与mongodb 有很大差异,这里自己用到一些操作方式(查询 更新 批量删除)做一些说明。

所有操作均基于:nodejs aws-sdk
上篇blog 链接:https://blog.csdn.net/m0_37263637/article/details/80501548
关于AWS Dynamdb 操作费用及流量及查询优化部分有空再写篇文档介绍。

官方文档:
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html

文中后续所有代码均是基于下列code:

var AWS = require("aws-sdk");
AWS.config.update({
  region: "us-west-2",                          //使用哪个区域的aws服务
  endpoint: "http://localhost:8000" //dynamodb位置
});
var db = new AWS.DynamoDB();
var docClient = new AWS.DynamoDB.DocumentClient();

DynamoDB 中绝大部分操作都与主键有关。

1 查询数据

1.1 查询主键属性数据

直接使用db.get接口即可。

1.2 查询满足条件的主键数据

使用db.query接口:

let params = {};
params.TableName = "files";
let keyConditions = {};
keyConditions.deviceid = {"ComparisonOperator": "EQ", "AttributeValueList": [deviceid]};
keyConditions.date = {"ComparisonOperator": "BETWEEN", "AttributeValueList": [startday, endday]};
params.KeyConditions = keyConditions;

db.query(params, function (err, _result) {

}

获取某个设备下,某段时间内的文件信息。文件表 以设备id 为分区键,date为排序键。
PS:DynamoDB 不支持date类型,我们以unix时间戳形式存储date。

关于条件表达式:

EQ:    等于 LE:小于或等于。LT: 少于。GE:大于或等于。
BEGINS_WITH :检查前缀。BETWEEN :大于或等于第一个值,小于或等于第二个值。

上诉code,条件即为分区键等于传入参数deviceid 且排序键在传入参数startday, endday之间的items。

1.3 查询非主键属性数据 (索引)

DynamoDB 是没有办法像mongodb那样非常方便地查询非主键属性数据,很多应用程序可能适合有一个或多个二级(或替代)键,以便通过主键以外的属性对数据进行高效访问。要解决此问题,您可以对表创建一个或多个二级索引,然后对这些索引发出 Query 或 Scan 请求。这里说明一下scan请求需要仔细评估后使用,因为可能带来很大性能消耗。

1.3.1 索引基本概念

二级索引 是一种数据结构,它包含表中属性的子集以及一个支持 Query 操作的替代键
您可以使用 Query 从索引中检索数据,其方式与对表使用 Query 大致相同。一个表可以有多个二级索引,这样,应用程序可以访问许多不同的查询模式。

每个二级索引关联且仅关联一个表,并从该表中获取其数据。这称为索引的基表。在创建索引时,您为索引定义一个替代键 (分区键和排序键)。
您还需要定义从基表投影或复制到索引的属性。DynamoDB 将这些属性与基表中的主键属性一起复制到索引中。
然后,您可以查询或扫描该索引,就像查询或扫描表一样。每个 二级索引 关联且仅关联一个表,并从该表中获取其数据。这称为索引的基表。

DynamoDB 把索引分为了两类:全局索二级引和本地二级索引。主要区别即为:

  1. 全局索引(Global secondary index):索引分区键和排序键与基表分区键排序键不同,全局二级索引被视为“全局”,是因为对索引执行的查询可以跨基表中所有分区的所有数据。
  2. 本地二级索引:分区键与基表相同但排序键不同的索引。local secondary index的含义是“本地”,表示local secondary index的每个分区的范围都限定为具有相同分区键值的基表分区。

1.3.2 创建索引

索引的一些基础限制:
索引的名称。索引的命名规则与表的命名规则相同,
索引的键架构。索引键架构中的每个属性必须是类型为字符串、数字或二进制的顶级属性。其他数据类型,包括文档和集,均不受支持。
每个表创建最多 5 个 全局二级索引和最多 5 个local secondary index。

上篇blog我们建立一个Movie表,现在我们为Movie表中添加一个id属性: { AttributeName: “id”, AttributeType: “S” }

var params = {
    TableName : "Movies",//表名
    KeySchema: [       //主键
        { AttributeName: "year", KeyType: "HASH"},  //Partition key
        { AttributeName: "title", KeyType: "RANGE" }  //Sort key
    ],
    AttributeDefinitions: [       
        { AttributeName: "year", AttributeType: "N" },
        { AttributeName: "title", AttributeType: "S" }
        { AttributeName: "id", AttributeType: "S" }
    ],
    ProvisionedThroughput: {       
        ReadCapacityUnits: 10, 
        WriteCapacityUnits: 10
    }
};

现在在Movie表上建立全局二级索引
我们ProvisionedThroughput属性 后加上全局二级索引属性,该索引以id为主键

 "GlobalSecondaryIndexes": [
      {
        "IndexName": "id_index",
        "KeySchema": [
          {
            "AttributeName": "id",
            "KeyType": "HASH"
          }
        ],
        "Projection": {
          "ProjectionType": "ALL"//索引包含基本的属性,我们设置为都包含,即基本中有的属性索引中均有
            //"NonKeyAttributes": ["_id", "info"],
           //"ProjectionType": "INCLUDE"
        },
        "ProvisionedThroughput": {
          "ReadCapacityUnits": 20,
          "WriteCapacityUnits": 10
        }
      }

ProjectionType 哪些属性会被复制到索引中,可选参数:

  • KEYS_ONLY - Only the index and primary keys are projected into the index.(主键)
  • INCLUDE - Only the specified table attributes are projected into the index. The list of projected attributes are in NonKeyAttributes.(部分属性复制)
  • ALL - All of the table attributes areprojected into the index.(所有属性均复制)

NonKeyAttributes - 哪些非主键属性会被投影到二级索引中,该参数不能超过20个。

当我们建立这个带索引的表时索引已经被创建好了。同样耶使用 UpdateTable 操作并指定 GlobalSecondaryIndexUpdates 来将global secondary index添加到现有表中。接下里介绍如何使用

1.3.3 查询索引

 let params = {};
 params.TableName = "Movies";
 params.IndexName = "id_index";
 let keyConditions = {};
 keyConditions._id = {"ComparisonOperator": "EQ", "AttributeValueList": ["ab128def4e2a3c"]};
 params.KeyConditions = keyConditions;
 db.query(params, function (err, _result) {
     console.log(result);//查询结果
  }

同样我们也可以使用ProjectionExpression参数来返回指定的属性

1.3.4 索引一些限制

索引不能用来更新数据,仅能用于查询得到表的主键后,使用表操作进行更新。
索引开销需要精确计算因为索引的性质相当于在基表上又建了一个表,具体可参考:
https://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/GSI.html#GSI.Writes

2 更新数据

直接使用db.update更新即可。详细内容可参见上篇blog。

2.1 更新操作返回值的问题

更新接口可以配置该参数:
params.ReturnValues = “ALL_NEW”;

  • ALL_NEW 更新后的该条项目items所有值
  • ALL_OLD 更新前的该条项目所有值
  • UPDATED_OLD返回要更新的值原来的值(复合值即为该整个值)
  • UPDATED_NEW 返回要更新的值更新后的值。
    ReturnValues: NONE | ALL_OLD | UPDATED_OLD | ALL_NEW | UPDATED_NEW

2.2 复合类型更新遇到的问题

但这里遇到一个问题,复合类型更新时,表现更新为覆盖更新。现有实现是
将旧的复合类型赋值个一个变量,再将要更新(添加)的值赋值给该变量,最后用变量去更新这个数据。
PS:解决了再来处理

3 批量添加数据/批量删除数据

如果添加/删除单个数据可以使用 db.put db.delete 基础操作。当有时涉及到需要将分区键下的所有数据均清空就需要用到批量删除,因为只用到批量删除就只介绍这个了。
函数:

batchWrite(params = {}, callback) ⇒ AWS.Request。

参数:

var params = {
  RequestItems: { /* required */
    '<TableName>': [//待操作表名
      {
        DeleteRequest: {  //第一个删除请求
            Key: { /* required */ 待操作item 主键}
      }
    },
    {
      DeleteRequest{   //第二个删除请求
           Key: { /* required */ 待操作item 主键}
       }
    }
    …….

    ]
    }
    }

要使用batchWrite我们就需要构造这种参数形式。

Sample :
该段代码准对已userid 为分区键,date为排序键的表进行删除操作,此代码想userid 分区键下的所有items。
1 首先我们要删除整个userid 分区键下的item,即需要得到所有items的完整主键,以userid为主键,小于当前时间的条件进行查询,得到整个userid 分区键下的所有items信息。
2 构造batchWrite 参数形式,并将query返回的查询结果的主键依次添加进batchWrite 参数

batchWrite使用注意事项:

  • 单次操作不能超过25个请求。
  • 单个项目不能超过400 KB。
  • 总请求大小不能超过16 MB。

Code

let requestArrayCount = new Array();
let requestItems = {};
requestItems[params.TableName] = new Array();

_result.Items.forEach(function(currentValue, index, arr){//构造参数
    requestItems[params.TableName].push({DeleteRequest:{Key:{userid :currentValue.userid , date:currentValue.date}} });
            if(((parseInt(index)+1)%25) === 0 || parseInt(index) ===  _result.Items.length -1){
                requestArrayCount.push(requestItems);
                requestItems= {};
                requestItems[params.TableName] = [];
            }
});

let param ={};
requestArrayCount.forEach(function(currentValue, index, arr){//删除
let filesTable ={};
param.RequestItems =  currentValue;
db.batchWrite(param, function (err) {
     if (err) {
                    logger.debug(err);
                    return callback({"file": err});
                }  
        });
})

猜你喜欢

转载自blog.csdn.net/m0_37263637/article/details/80523196