DynamoDB Query查询 #

一、Query概述 #

1.1 Query特点 #

text
Query特点:
├── 基于主键查询
├── 必须指定分区键
├── 可选指定排序键条件
├── 高效,使用索引
└── 支持过滤、排序、分页

1.2 Query vs Scan #

特性 Query Scan
查询方式 基于索引 全表扫描
效率
成本
适用场景 已知分区键 未知分区键

二、基本查询 #

2.1 使用CLI #

bash
aws dynamodb query \
  --table-name Orders \
  --key-condition-expression 'UserId = :uid' \
  --expression-attribute-values '{":uid": {"S": "user123"}}'

2.2 使用JavaScript SDK #

javascript
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient, QueryCommand } = require('@aws-sdk/lib-dynamodb');

const client = new DynamoDBClient({ region: 'us-east-1' });
const docClient = DynamoDBDocumentClient.from(client);

const response = await docClient.send(new QueryCommand({
  TableName: 'Orders',
  KeyConditionExpression: 'UserId = :uid',
  ExpressionAttributeValues: {
    ':uid': 'user123'
  }
}));

console.log(response.Items);

2.3 使用Python SDK #

python
import boto3

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Orders')

response = table.query(
    KeyConditionExpression='UserId = :uid',
    ExpressionAttributeValues={
        ':uid': 'user123'
    }
)

print(response['Items'])

三、键条件表达式 #

3.1 分区键条件 #

分区键只能使用相等条件:

javascript
// 相等条件
KeyConditionExpression: 'UserId = :uid'

// 使用ExpressionAttributeNames
KeyConditionExpression: '#pk = :uid',
ExpressionAttributeNames: {
  '#pk': 'UserId'
}

3.2 排序键条件 #

排序键支持多种条件:

javascript
// 相等
KeyConditionExpression: 'UserId = :uid AND OrderId = :oid'

// 小于
KeyConditionExpression: 'UserId = :uid AND OrderId < :oid'

// 小于等于
KeyConditionExpression: 'UserId = :uid AND OrderId <= :oid'

// 大于
KeyConditionExpression: 'UserId = :uid AND OrderId > :oid'

// 大于等于
KeyConditionExpression: 'UserId = :uid AND OrderId >= :oid'

// 范围
KeyConditionExpression: 'UserId = :uid AND OrderId BETWEEN :start AND :end'

// 前缀匹配
KeyConditionExpression: 'UserId = :uid AND begins_with(OrderId, :prefix)'

3.3 排序键条件示例 #

查询特定订单:

javascript
const response = await docClient.send(new QueryCommand({
  TableName: 'Orders',
  KeyConditionExpression: 'UserId = :uid AND OrderId = :oid',
  ExpressionAttributeValues: {
    ':uid': 'user123',
    ':oid': 'order001'
  }
}));

查询日期范围内的订单:

javascript
const response = await docClient.send(new QueryCommand({
  TableName: 'Orders',
  KeyConditionExpression: 'UserId = :uid AND OrderDate BETWEEN :start AND :end',
  ExpressionAttributeValues: {
    ':uid': 'user123',
    ':start': '2024-01-01',
    ':end': '2024-12-31'
  }
}));

查询特定前缀的订单:

javascript
const response = await docClient.send(new QueryCommand({
  TableName: 'Orders',
  KeyConditionExpression: 'UserId = :uid AND begins_with(OrderId, :prefix)',
  ExpressionAttributeValues: {
    ':uid': 'user123',
    ':prefix': 'ORDER#2024'
  }
}));

四、过滤表达式 #

4.1 基本过滤 #

FilterExpression在查询后过滤,不减少RCU消耗:

javascript
const response = await docClient.send(new QueryCommand({
  TableName: 'Orders',
  KeyConditionExpression: 'UserId = :uid',
  FilterExpression: 'Status = :status',
  ExpressionAttributeValues: {
    ':uid': 'user123',
    ':status': 'COMPLETED'
  }
}));

4.2 过滤条件类型 #

javascript
// 比较运算
FilterExpression: 'TotalAmount > :minAmount'
FilterExpression: 'Status = :status'
FilterExpression: 'Age BETWEEN :min AND :max'

// 逻辑运算
FilterExpression: 'Status = :status AND TotalAmount > :minAmount'
FilterExpression: 'Status = :status OR Status = :status2'
FilterExpression: 'NOT Status = :status'

// 属性检查
FilterExpression: 'attribute_exists(Email)'
FilterExpression: 'attribute_not_exists(DeletedAt)'
FilterExpression: 'attribute_type(Age, :type)'

// 字符串操作
FilterExpression: 'begins_with(Name, :prefix)'
FilterExpression: 'contains(Tags, :tag)'

// 集合操作
FilterExpression: 'contains(Categories, :category)'

// 大小检查
FilterExpression: 'size(Description) < :maxSize'

// IN操作
FilterExpression: 'Status IN (:s1, :s2, :s3)'

4.3 组合过滤条件 #

javascript
const response = await docClient.send(new QueryCommand({
  TableName: 'Orders',
  KeyConditionExpression: 'UserId = :uid',
  FilterExpression: `
    Status = :status 
    AND TotalAmount > :minAmount 
    AND attribute_not_exists(DeletedAt)
  `,
  ExpressionAttributeValues: {
    ':uid': 'user123',
    ':status': 'COMPLETED',
    ':minAmount': 100
  }
}));

五、排序 #

5.1 排序方向 #

javascript
// 升序(默认)
const response = await docClient.send(new QueryCommand({
  TableName: 'Orders',
  KeyConditionExpression: 'UserId = :uid',
  ExpressionAttributeValues: { ':uid': 'user123' },
  ScanIndexForward: true  // 升序
}));

// 降序
const response = await docClient.send(new QueryCommand({
  TableName: 'Orders',
  KeyConditionExpression: 'UserId = :uid',
  ExpressionAttributeValues: { ':uid': 'user123' },
  ScanIndexForward: false  // 降序
}));

5.2 排序示例 #

获取最新订单:

javascript
const response = await docClient.send(new QueryCommand({
  TableName: 'Orders',
  KeyConditionExpression: 'UserId = :uid',
  ExpressionAttributeValues: { ':uid': 'user123' },
  ScanIndexForward: false,  // 降序
  Limit: 1  // 只取第一个
}));

const latestOrder = response.Items[0];

获取排行榜Top 10:

javascript
const response = await docClient.send(new QueryCommand({
  TableName: 'Leaderboard',
  KeyConditionExpression: 'GameId = :gid',
  ExpressionAttributeValues: { ':gid': 'game001' },
  ScanIndexForward: false,  // 分数从高到低
  Limit: 10
}));

六、分页 #

6.1 使用Limit #

javascript
// 获取前10条
const response = await docClient.send(new QueryCommand({
  TableName: 'Orders',
  KeyConditionExpression: 'UserId = :uid',
  ExpressionAttributeValues: { ':uid': 'user123' },
  Limit: 10
}));

console.log(`Items: ${response.Items.length}`);
console.log(`Has more: ${response.LastEvaluatedKey ? true : false}`);

6.2 使用ExclusiveStartKey #

javascript
async function queryWithPagination(params) {
  let items = [];
  let lastKey = null;
  
  do {
    const response = await docClient.send(new QueryCommand({
      ...params,
      ExclusiveStartKey: lastKey
    }));
    
    items.push(...response.Items);
    lastKey = response.LastEvaluatedKey;
  } while (lastKey);
  
  return items;
}

// 使用示例
const allOrders = await queryWithPagination({
  TableName: 'Orders',
  KeyConditionExpression: 'UserId = :uid',
  ExpressionAttributeValues: { ':uid': 'user123' }
});

6.3 分页工具函数 #

javascript
async function* queryPaginator(params) {
  let lastKey = null;
  
  do {
    const response = await docClient.send(new QueryCommand({
      ...params,
      ExclusiveStartKey: lastKey
    }));
    
    yield response.Items;
    lastKey = response.LastEvaluatedKey;
  } while (lastKey);
}

// 使用示例
for await (const batch of queryPaginator({
  TableName: 'Orders',
  KeyConditionExpression: 'UserId = :uid',
  ExpressionAttributeValues: { ':uid': 'user123' },
  Limit: 100
})) {
  for (const item of batch) {
    console.log(item);
  }
}

七、投影 #

7.1 指定返回属性 #

javascript
const response = await docClient.send(new QueryCommand({
  TableName: 'Users',
  KeyConditionExpression: 'UserId = :uid',
  ProjectionExpression: 'UserId, Name, Email',
  ExpressionAttributeValues: { ':uid': 'user123' }
}));

7.2 嵌套属性投影 #

javascript
const response = await docClient.send(new QueryCommand({
  TableName: 'Users',
  KeyConditionExpression: 'UserId = :uid',
  ProjectionExpression: 'UserId, Name, Address.City, Address.Country',
  ExpressionAttributeValues: { ':uid': 'user123' }
}));

八、使用索引查询 #

8.1 查询全局二级索引 #

javascript
const response = await docClient.send(new QueryCommand({
  TableName: 'Users',
  IndexName: 'EmailIndex',
  KeyConditionExpression: 'Email = :email',
  ExpressionAttributeValues: {
    ':email': 'john@example.com'
  }
}));

8.2 查询本地二级索引 #

javascript
const response = await docClient.send(new QueryCommand({
  TableName: 'Orders',
  IndexName: 'OrderDateIndex',
  KeyConditionExpression: 'UserId = :uid AND OrderDate BETWEEN :start AND :end',
  ExpressionAttributeValues: {
    ':uid': 'user123',
    ':start': '2024-01-01',
    ':end': '2024-12-31'
  }
}));

九、一致性读取 #

9.1 强一致性读取 #

javascript
const response = await docClient.send(new QueryCommand({
  TableName: 'Orders',
  KeyConditionExpression: 'UserId = :uid',
  ExpressionAttributeValues: { ':uid': 'user123' },
  ConsistentRead: true  // 强一致性
}));

9.2 一致性选择 #

text
一致性选择:
├── 最终一致性(默认)
│   ├── 消耗1 RCU(4KB)
│   ├── 延迟较低
│   └── 可能读到旧数据
└── 强一致性
    ├── 消耗2 RCU(4KB)
    ├── 延迟略高
    └── 保证读到最新数据

十、实用示例 #

10.1 获取用户所有订单 #

javascript
async function getUserOrders(userId, options = {}) {
  const { limit, lastKey, status } = options;
  
  const params = {
    TableName: 'Orders',
    KeyConditionExpression: 'UserId = :uid',
    ExpressionAttributeValues: {
      ':uid': userId
    },
    ScanIndexForward: false  // 最新订单在前
  };
  
  if (limit) params.Limit = limit;
  if (lastKey) params.ExclusiveStartKey = lastKey;
  if (status) {
    params.FilterExpression = 'Status = :status';
    params.ExpressionAttributeValues[':status'] = status;
  }
  
  return await docClient.send(new QueryCommand(params));
}

10.2 获取时间范围内的数据 #

javascript
async function getDataInRange(partitionKey, startTime, endTime) {
  const items = [];
  let lastKey = null;
  
  do {
    const response = await docClient.send(new QueryCommand({
      TableName: 'TimeSeries',
      KeyConditionExpression: 'PK = :pk AND SK BETWEEN :start AND :end',
      ExpressionAttributeValues: {
        ':pk': partitionKey,
        ':start': startTime,
        ':end': endTime
      },
      ExclusiveStartKey: lastKey
    }));
    
    items.push(...response.Items);
    lastKey = response.LastEvaluatedKey;
  } while (lastKey);
  
  return items;
}

10.3 查询排行榜 #

javascript
async function getLeaderboard(gameId, limit = 10) {
  const response = await docClient.send(new QueryCommand({
    TableName: 'Leaderboard',
    KeyConditionExpression: 'GameId = :gid',
    ExpressionAttributeValues: {
      ':gid': gameId
    },
    ScanIndexForward: false,  // 分数降序
    Limit: limit
  }));
  
  return response.Items.map((item, index) => ({
    rank: index + 1,
    ...item
  }));
}

10.4 查询用户周边数据 #

javascript
async function getUserRelatedData(userId) {
  // 并行查询多种数据
  const [profile, orders, addresses] = await Promise.all([
    // 用户资料
    docClient.send(new QueryCommand({
      TableName: 'AppTable',
      KeyConditionExpression: 'PK = :pk AND SK = :sk',
      ExpressionAttributeValues: {
        ':pk': `USER#${userId}`,
        ':sk': 'PROFILE'
      }
    })),
    
    // 用户订单
    docClient.send(new QueryCommand({
      TableName: 'AppTable',
      KeyConditionExpression: 'PK = :pk AND begins_with(SK, :prefix)',
      ExpressionAttributeValues: {
        ':pk': `USER#${userId}`,
        ':prefix': 'ORDER#'
      },
      Limit: 10,
      ScanIndexForward: false
    })),
    
    // 用户地址
    docClient.send(new QueryCommand({
      TableName: 'AppTable',
      KeyConditionExpression: 'PK = :pk AND begins_with(SK, :prefix)',
      ExpressionAttributeValues: {
        ':pk': `USER#${userId}`,
        ':prefix': 'ADDRESS#'
      }
    }))
  ]);
  
  return {
    profile: profile.Items[0],
    orders: orders.Items,
    addresses: addresses.Items
  };
}

十一、性能优化 #

11.1 查询优化建议 #

text
优化建议:
├── 使用Query而非Scan
├── 合理设计排序键
├── 使用索引覆盖查询
├── 避免过度使用FilterExpression
├── 合理设置Limit
└── 使用投影减少数据传输

11.2 RCU消耗计算 #

text
RCU计算:
├── 最终一致性:1 RCU / 4KB
├── 强一致性:2 RCU / 4KB
└── 向上取整

示例:
├── 查询返回8KB数据(最终一致)= 2 RCU
├── 查询返回8KB数据(强一致)= 4 RCU
└── FilterExpression不影响RCU

十二、总结 #

Query操作要点:

特性 说明
键条件 必须指定分区键
排序键条件 支持多种条件
过滤表达式 查询后过滤
排序 ScanIndexForward
分页 Limit + ExclusiveStartKey
投影 ProjectionExpression

下一步,让我们学习Scan扫描操作!

最后更新:2026-03-27