DynamoDB高级查询 #
一、高级查询概述 #
1.1 查询能力 #
text
DynamoDB查询能力:
├── 键条件查询
├── 过滤表达式
├── 投影表达式
├── 排序和分页
├── 索引查询
└── 批量查询
1.2 查询限制 #
text
查询限制:
├── 不支持JOIN
├── 不支持GROUP BY
├── 不支持HAVING
├── 排序键排序有限制
└── 过滤不减少RCU
二、复杂键条件 #
2.1 排序键范围查询 #
javascript
// 日期范围
KeyConditionExpression: 'PK = :pk AND CreatedAt BETWEEN :start AND :end'
// 数值范围
KeyConditionExpression: 'PK = :pk AND Score BETWEEN :min AND :max'
// 字符串范围
KeyConditionExpression: 'PK = :pk AND Name BETWEEN :a AND :z'
2.2 前缀匹配 #
javascript
// 查询特定前缀的订单
const response = await docClient.send(new QueryCommand({
TableName: 'Orders',
KeyConditionExpression: 'UserId = :uid AND begins_with(OrderId, :prefix)',
ExpressionAttributeValues: {
':uid': 'user123',
':prefix': 'ORDER#2024-01'
}
}));
2.3 复合排序键设计 #
javascript
// 排序键设计:STATUS#PRIORITY#TIMESTAMP
// 示例:OPEN#HIGH#2024-01-01T10:00:00
// 查询高优先级的开放任务
const response = await docClient.send(new QueryCommand({
TableName: 'Tasks',
KeyConditionExpression: 'ProjectId = :pid AND begins_with(SK, :prefix)',
ExpressionAttributeValues: {
':pid': 'project001',
':prefix': 'OPEN#HIGH'
}
}));
三、高级过滤表达式 #
3.1 属性存在性检查 #
javascript
// 检查属性存在
FilterExpression: 'attribute_exists(Email)'
// 检查属性不存在
FilterExpression: 'attribute_not_exists(DeletedAt)'
// 检查属性类型
FilterExpression: 'attribute_type(Age, :type)',
ExpressionAttributeValues: { ':type': 'N' }
3.2 字符串操作 #
javascript
// 前缀匹配
FilterExpression: 'begins_with(Name, :prefix)'
// 包含子串
FilterExpression: 'contains(Description, :keyword)'
// 组合字符串条件
FilterExpression: 'begins_with(Email, :prefix) AND contains(Email, :domain)'
3.3 集合操作 #
javascript
// 检查集合包含元素
FilterExpression: 'contains(Tags, :tag)'
// 检查多个标签(OR)
FilterExpression: 'contains(Tags, :tag1) OR contains(Tags, :tag2)'
// 检查属性是否在集合中
FilterExpression: 'Status IN (:s1, :s2, :s3)'
3.4 大小函数 #
javascript
// 字符串长度
FilterExpression: 'size(Description) < :maxSize'
// 列表长度
FilterExpression: 'size(Items) > :minItems'
// 检查非空字符串
FilterExpression: 'size(Name) > :zero',
ExpressionAttributeValues: { ':zero': 0 }
3.5 嵌套属性过滤 #
javascript
// 过滤嵌套Map属性
FilterExpression: 'Address.City = :city'
// 过滤嵌套List属性
FilterExpression: 'contains(Items[0].Category, :category)'
// 深层嵌套
FilterExpression: 'Profile.Contacts.Email = :email'
四、表达式属性名 #
4.1 处理保留字 #
javascript
const response = await docClient.send(new QueryCommand({
TableName: 'Users',
KeyConditionExpression: '#pk = :pk',
FilterExpression: '#n = :name AND #s = :status',
ExpressionAttributeNames: {
'#pk': 'UserId',
'#n': 'Name', // Name是保留字
'#s': 'Status' // Status是保留字
},
ExpressionAttributeValues: {
':pk': 'user123',
':name': 'John Doe',
':status': 'active'
}
}));
4.2 常见保留字 #
text
DynamoDB保留字:
├── Name, Names
├── Status, State
├── Date, Year, Month, Day
├── User, Order, Key, Value
├── Data, Timestamp
├── Index, Table, Item
├── Number, String, Boolean
└── 更多见AWS文档
4.3 动态属性名 #
javascript
function buildQueryWithDynamicField(fieldName, value) {
return {
TableName: 'Users',
KeyConditionExpression: 'UserId = :pk',
FilterExpression: '#field = :value',
ExpressionAttributeNames: {
'#field': fieldName
},
ExpressionAttributeValues: {
':pk': 'user123',
':value': value
}
};
}
五、查询模式设计 #
5.1 单表多实体查询 #
javascript
// 表设计:
// PK: ENTITY#ID
// SK: TYPE#SUBTYPE
// 查询用户所有数据
async function getUserData(userId) {
const response = await docClient.send(new QueryCommand({
TableName: 'AppTable',
KeyConditionExpression: 'PK = :pk',
ExpressionAttributeValues: {
':pk': `USER#${userId}`
}
}));
// 按类型分组
const grouped = {
profile: null,
orders: [],
addresses: []
};
for (const item of response.Items) {
if (item.SK === 'PROFILE') {
grouped.profile = item;
} else if (item.SK.startsWith('ORDER#')) {
grouped.orders.push(item);
} else if (item.SK.startsWith('ADDRESS#')) {
grouped.addresses.push(item);
}
}
return grouped;
}
5.2 时间序列查询 #
javascript
// 设计:PK = DEVICE#ID, SK = TIMESTAMP
// 查询最近N条数据
async function getRecentData(deviceId, limit = 100) {
const response = await docClient.send(new QueryCommand({
TableName: 'TimeSeries',
KeyConditionExpression: 'PK = :pk',
ExpressionAttributeValues: {
':pk': `DEVICE#${deviceId}`
},
ScanIndexForward: false, // 降序
Limit: limit
}));
return response.Items;
}
// 查询时间范围数据
async function getDataInRange(deviceId, startTime, endTime) {
const response = await docClient.send(new QueryCommand({
TableName: 'TimeSeries',
KeyConditionExpression: 'PK = :pk AND SK BETWEEN :start AND :end',
ExpressionAttributeValues: {
':pk': `DEVICE#${deviceId}`,
':start': startTime,
':end': endTime
}
}));
return response.Items;
}
5.3 层级数据查询 #
javascript
// 设计:
// PK = PARENT#ID
// SK = CHILD#TYPE#ID
// 查询所有子项
async function getChildren(parentId, childType = null) {
const params = {
TableName: 'Hierarchy',
KeyConditionExpression: 'PK = :pk',
ExpressionAttributeValues: {
':pk': `PARENT#${parentId}`
}
};
if (childType) {
params.KeyConditionExpression += ' AND begins_with(SK, :type)';
params.ExpressionAttributeValues[':type'] = `CHILD#${childType}`;
}
return await docClient.send(new QueryCommand(params));
}
六、索引查询策略 #
6.1 GSI查询 #
javascript
// 通过Email查询用户
async function getUserByEmail(email) {
const response = await docClient.send(new QueryCommand({
TableName: 'Users',
IndexName: 'EmailIndex',
KeyConditionExpression: 'Email = :email',
ExpressionAttributeValues: {
':email': email
}
}));
return response.Items[0];
}
6.2 GSI过载模式 #
javascript
// 一个GSI支持多种查询
// GSI1PK = QUERY_TYPE#VALUE
// GSI1SK = TIMESTAMP
// 按状态查询订单
async function getOrdersByStatus(status, limit = 100) {
return await docClient.send(new QueryCommand({
TableName: 'Orders',
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk',
ExpressionAttributeValues: {
':pk': `STATUS#${status}`
},
ScanIndexForward: false,
Limit: limit
}));
}
// 按日期查询订单
async function getOrdersByDate(date, limit = 100) {
return await docClient.send(new QueryCommand({
TableName: 'Orders',
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk',
ExpressionAttributeValues: {
':pk': `DATE#${date}`
},
ScanIndexForward: false,
Limit: limit
}));
}
6.3 稀疏索引查询 #
javascript
// 只有有Phone属性的用户会被索引
async function getUsersWithPhone(phoneNumber) {
const response = await docClient.send(new QueryCommand({
TableName: 'Users',
IndexName: 'PhoneIndex',
KeyConditionExpression: 'Phone = :phone',
ExpressionAttributeValues: {
':phone': phoneNumber
}
}));
return response.Items;
}
七、查询优化技巧 #
7.1 减少过滤数据 #
javascript
// 不好的做法:大量过滤
const response = await docClient.send(new QueryCommand({
TableName: 'Orders',
KeyConditionExpression: 'UserId = :uid',
FilterExpression: 'Status = :status AND TotalAmount > :min',
ExpressionAttributeValues: {
':uid': 'user123',
':status': 'COMPLETED',
':min': 100
}
}));
// 好的做法:使用索引
// 创建GSI:PK=Status, SK=TotalAmount
const response = await docClient.send(new QueryCommand({
TableName: 'Orders',
IndexName: 'StatusAmountIndex',
KeyConditionExpression: 'Status = :status AND TotalAmount > :min',
ExpressionAttributeValues: {
':status': 'COMPLETED',
':min': 100
}
}));
7.2 使用投影 #
javascript
// 只获取需要的属性
const response = await docClient.send(new QueryCommand({
TableName: 'Users',
KeyConditionExpression: 'UserId = :uid',
ProjectionExpression: 'UserId, Name, Email',
ExpressionAttributeValues: {
':uid': 'user123'
}
}));
7.3 批量查询优化 #
javascript
// 并行查询多个分区
async function batchQuery(partitionKeys) {
const promises = partitionKeys.map(pk =>
docClient.send(new QueryCommand({
TableName: 'Orders',
KeyConditionExpression: 'PK = :pk',
ExpressionAttributeValues: { ':pk': pk }
}))
);
const results = await Promise.all(promises);
return results.flatMap(r => r.Items);
}
八、复杂查询示例 #
8.1 分页查询 #
javascript
async function paginatedQuery(params) {
const { pageSize = 20, lastKey } = params;
const response = await docClient.send(new QueryCommand({
TableName: params.tableName,
KeyConditionExpression: params.keyCondition,
FilterExpression: params.filterExpression,
ExpressionAttributeValues: params.values,
Limit: pageSize,
ExclusiveStartKey: lastKey
}));
return {
items: response.Items,
lastKey: response.LastEvaluatedKey,
hasMore: !!response.LastEvaluatedKey
};
}
8.2 搜索查询 #
javascript
async function searchItems(tableName, searchTerm, options = {}) {
const { fields = ['Name', 'Description'], limit = 20 } = options;
// 构建过滤表达式
const filterParts = fields.map((field, i) => `contains(#f${i}, :term)`);
const expressionAttributeNames = {};
fields.forEach((field, i) => {
expressionAttributeNames[`#f${i}`] = field;
});
const response = await docClient.send(new ScanCommand({
TableName: tableName,
FilterExpression: filterParts.join(' OR '),
ExpressionAttributeNames,
ExpressionAttributeValues: {
':term': searchTerm
},
Limit: limit
}));
return response.Items;
}
8.3 聚合查询 #
javascript
async function aggregateByField(tableName, fieldName, keyCondition, values) {
const counts = {};
let lastKey = null;
do {
const response = await docClient.send(new QueryCommand({
TableName: tableName,
KeyConditionExpression: keyCondition,
ExpressionAttributeValues: values,
ExclusiveStartKey: lastKey
}));
for (const item of response.Items) {
const fieldValue = item[fieldName];
counts[fieldValue] = (counts[fieldValue] || 0) + 1;
}
lastKey = response.LastEvaluatedKey;
} while (lastKey);
return counts;
}
// 使用示例
const statusCounts = await aggregateByField(
'Orders',
'Status',
'UserId = :uid',
{ ':uid': 'user123' }
);
console.log(statusCounts);
// { PENDING: 5, COMPLETED: 10, CANCELLED: 2 }
九、错误处理 #
9.1 查询错误处理 #
javascript
async function safeQuery(params) {
try {
return await docClient.send(new QueryCommand(params));
} catch (error) {
switch (error.name) {
case 'ProvisionedThroughputExceededException':
console.error('超过预置吞吐量');
break;
case 'ResourceNotFoundException':
console.error('表或索引不存在');
break;
case 'ValidationException':
console.error('参数验证失败:', error.message);
break;
default:
console.error('查询错误:', error);
}
throw error;
}
}
9.2 重试机制 #
javascript
async function queryWithRetry(params, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await docClient.send(new QueryCommand(params));
} catch (error) {
if (error.name === 'ProvisionedThroughputExceededException' && i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 100));
continue;
}
throw error;
}
}
}
十、最佳实践 #
10.1 查询设计原则 #
text
设计原则:
├── 以访问模式为中心
├── 使用索引支持查询
├── 避免过度使用Scan
├── 合理设计排序键
└── 使用投影减少数据传输
10.2 性能优化 #
text
性能建议:
├── 使用Query而非Scan
├── 使用索引覆盖查询
├── 合理设置Limit
├── 使用并行查询
└── 监控查询性能
十一、总结 #
高级查询要点:
| 技术 | 说明 |
|---|---|
| 复杂键条件 | 范围、前缀匹配 |
| 过滤表达式 | 多种条件组合 |
| 表达式属性名 | 处理保留字 |
| 索引查询 | GSI/LSI |
| 查询优化 | 减少过滤、使用投影 |
下一步,让我们学习索引设计!
最后更新:2026-03-27