【问题标题】:error with dynamo occurred (ValidationException) when calling the Query operation: Invalid KeyConditionExpression:调用查询操作时发生发电机错误(ValidationException):KeyConditionExpression 无效:
【发布时间】:2021-09-03 06:52:21
【问题描述】:

我对 dynamodb 有点陌生

查看我在尝试使用以下链接中 StackOverflow 帖子下方的说明在 python lambda 函数中获取我的 dynamodb 表的最大 id 时遇到的错误 Dynamodb max value

 An error occurred (ValidationException) when calling the Query operation: Invalid KeyConditionExpression: The expression can not be empty;\"}"

请看下面我的 lambda 函数代码

import json
import boto3

TABLE_NAME = 'user-profiles'

dynamo_DB = boto3.resource('dynamodb')    

def lambda_handler(event, context):
    user_id = event['user_id']
    email = event['email']
    bvn = event['bvn']
    password = event['password']
    phone = event['phone']
    gender = event['gender']

    output = ''


    if len(user_id) > 1 and len(password) > 5:
        try:


            table = dynamo_DB.Table(TABLE_NAME)

            values = list(table.query(
                KeyConditionExpression='',
                ScanIndexForward=False,
                Limit=1
                )
            )
            max_id = values[0]['id']
            new_id = max_id + 1

            Item = {
                'id': str(new_id),
                'profile-id': str(new_id),
                'user_id': user_id,
                'email': email,
                'bvn': bvn,
                'password': password,
                'phone': phone,
                'gender': gender
            }
            table.put_item(Item=Item)

            output += 'Data Inserted To Dynamodb Successfully'
        except Exception as e:
            output += 'error with dynamo registration ' + str(e)
            # print(output)

    else:
        output += 'invalid user or password entered, this is ' \
                  'what i received:\nusername: ' \
                  + str(user_id) + '\npassword: ' + str(password)

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": output,
        }),
    }
    # print(output)

【问题讨论】:

    标签: python amazon-dynamodb


    【解决方案1】:

    query 不能使用空的KeyConditionExpression,如果需要从表中读取所有记录,则需要使用scan。但是您不能在那里使用ScanIndexForward 来订购记录。

    似乎您正在尝试实现主键递增。我想警告你,你的解决方案并不是真的很棒,因为你很容易遇到竞争条件。

    我的建议是: 我猜你使用id 作为主键(又名分区键)。没关系。我要做的是在表格中插入一条额外的记录,比如increment 值:

    increment = table.update_item(
        Key={'id': 'increment'},
        UpdateExpression='ADD #increment :increment',
        ExpressionAttributeNames={'#increment': 'increment'},
        ExpressionAttributeValues={':increment': 1},
        ReturnValues='UPDATED_NEW',
    )
    
    new_id = increment['Attributes']['increment']
    

    此查询将使用id: 'increment' 更新现有记录并在记录中存储一个新的递增数字,如果这是第一个查询,则将使用increment: 1 创建记录,随后的调用将递增它。 ReturnValues 表示查询会在更新后返回结果,你会得到一个新的id。

    把代码放在你query最后一条记录的地方

    所以你的代码看起来像:

    import json
    import boto3
    
    TABLE_NAME = 'user-profiles'
    
    dynamo_DB = boto3.resource('dynamodb')    
    
    def lambda_handler(event, context):
        user_id = event['user_id']
        email = event['email']
        bvn = event['bvn']
        password = event['password']
        phone = event['phone']
        gender = event['gender']
    
        output = ''
    
    
        if len(user_id) > 1 and len(password) > 5:
            try:
    
    
                table = dynamo_DB.Table(TABLE_NAME)
    
                increment = table.update_item(
                    Key={'id': 'increment'},
                    UpdateExpression='ADD #increment :increment',
                    ExpressionAttributeNames={'#increment': 'increment'},
                    ExpressionAttributeValues={':increment': 1},
                    ReturnValues='UPDATED_NEW',
                )
    
                new_id = increment['Attributes']['increment']
    
                Item = {
                    'id': str(new_id),
                    'profile-id': str(new_id),
                    'user_id': user_id,
                    'email': email,
                    'bvn': bvn,
                    'password': password,
                    'phone': phone,
                    'gender': gender
                }
                table.put_item(Item=Item)
    
                output += 'Data Inserted To Dynamodb Successfully'
            except Exception as e:
                output += 'error with dynamo registration ' + str(e)
                # print(output)
    
        else:
            output += 'invalid user or password entered, this is ' \
                      'what i received:\nusername: ' \
                      + str(user_id) + '\npassword: ' + str(password)
    
        return {
            "statusCode": 200,
            "body": json.dumps({
                "message": output,
            }),
        }
        # print(output)
    

    你很好。

    额外的想法:

    并且要 100% 确保在递增时不存在竞争条件,您可以通过这种方式实现锁定机制:在递增之前,添加一条带有 id 值、locklock 属性的任意值的额外记录,并使用ConditionExpression='attribute_not_exists(lock)'。然后进行增量,然后通过删除记录lock 来释放锁。因此,当记录存在时,第二次“锁定”尝试会因属性lock 存在并抛出错误ConditionalCheckFailedException 的条件而中断(您可以捕获错误并向用户显示记录已被锁定或其他情况) .)

    抱歉,这里是 JavaScript 中的示例:

    module.exports.DynamoDbClient = class DynamoDbClient {
      constructor(tableName) {
        this.dynamoDb = new DynamoDB.DocumentClient();
        this.tableName = tableName;
      }
    
      async increment() {
        await this.lock();
    
        const {Attributes: {increment}} = await this.dynamoDb.update({
          TableName: this.tableName,
          Key: {id: 'increment'},
          UpdateExpression: 'ADD #increment :increment',
          ExpressionAttributeNames: {'#increment': 'increment'},
          ExpressionAttributeValues: {':increment': 1},
          ReturnValues: 'UPDATED_NEW',
        }).promise();
    
        await this.unlock();
    
        return increment;
      }
    
      async lock(key) {
        try {
          await this.dynamoDb.put({
            TableName: this.tableName,
            Item: {id: 'lock', _lock: true},
            ConditionExpression: 'attribute_not_exists(#lock)',
            ExpressionAttributeNames: {'#lock': '_lock'},
          }).promise();
        } catch (error) {
          if (error.code === 'ConditionalCheckFailedException') {
            throw new LockError(`Key is locked.`);
          }
    
          throw error;
        }
      }
    
      unlock() {
        return this.delete({id: 'lock'});
      }
    
      async delete(key) {
        await this.dynamoDb.delete({
          TableName: this.tableName,
          Key: key,
        }).promise();
      }
    }
    
    // usage
    
    const client = new DynamoDbClient('table');
    const newId = await client.increment();
    
    ...
    

    【讨论】:

    • 嗨@Floyd,你的回答太棒了。唯一的问题是我遇到了一个新错误:“调用 UpdateItem 操作时发生错误(ValidationException):提供的关键元素与架构不匹配”。请问如何解决这个问题
    • 我需要更多信息,您在表中设置了什么主键? profile-id 是排序键吗?
    • 是的@Floyd,字段“id”是分区键(主键),“profile-id”是排序键
    • 然后在更新增量查询中使用以下键:Key={'id': 'increment', 'profile-id': '#'},而不是 # 你可以使用任何值,你在增量键中使用什么值并不重要,这个想法是有一个额外记录将为您的新正常记录保留下一个增量
    • 非常感谢您的代码建议非常完美。你是个超级巨星。所以我欠你一杯。 :) 享受你的一天
    猜你喜欢
    • 2022-06-22
    • 2021-05-02
    • 1970-01-01
    • 1970-01-01
    • 2017-04-17
    • 1970-01-01
    • 2020-01-16
    • 2020-07-08
    • 1970-01-01
    相关资源
    最近更新 更多