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