aws-dynamodb
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAWS DynamoDB Skill
AWS DynamoDB 技能指南
Load with: base.md + [typescript.md | python.md]
DynamoDB is a fully managed NoSQL database designed for single-digit millisecond performance at any scale. Master single-table design and access pattern modeling.
加载方式: base.md + [typescript.md | python.md]
DynamoDB是一款全托管NoSQL数据库,专为任意规模下的个位数毫秒级性能设计。掌握单表设计与访问模式建模。
Core Principle
核心原则
Design for access patterns, not entities. Think access-pattern-first.
DynamoDB requires you to know your queries before designing your schema. Model around how you'll access data, not how data relates. Single-table design stores multiple entity types in one table using generic key attributes.
围绕访问模式而非实体设计。优先考虑访问模式。
DynamoDB要求你在设计schema之前明确查询需求。要围绕数据的访问方式建模,而非数据之间的关系。单表设计通过通用键属性将多种实体类型存储在同一个表中。
Key Concepts
关键概念
| Concept | Description |
|---|---|
| Partition Key (PK) | Primary key attribute - determines data distribution |
| Sort Key (SK) | Optional secondary key for range queries within partition |
| GSI | Global Secondary Index - alternate partition/sort keys |
| LSI | Local Secondary Index - same partition, different sort |
| Item | Single record (max 400 KB) |
| Attribute | Field within an item |
| 概念 | 描述 |
|---|---|
| Partition Key (PK) | 主键属性 - 决定数据分布 |
| Sort Key (SK) | 可选的二级键,用于分区内的范围查询 |
| GSI | 全局二级索引 - 备用分区/排序键 |
| LSI | 本地二级索引 - 相同分区,不同排序 |
| Item | 单条记录(最大400 KB) |
| Attribute | 条目内的字段 |
Single-Table Design
单表设计
Why Single Table?
为何选择单表设计?
- Fetch related data in single query
- Reduce round trips and costs
- Enable transactions across entity types
- Simplify operations (backup, restore, IAM)
- 一次查询获取相关数据
- 减少请求往返次数与成本
- 支持跨实体类型的事务
- 简化操作(备份、恢复、IAM权限管理)
Generic Key Pattern
通用键模式
typescript
// Instead of entity-specific keys:
// userId, orderId, productId
// Use generic keys that work for all entities:
interface BaseItem {
PK: string; // Partition Key
SK: string; // Sort Key
GSI1PK?: string; // First GSI partition key
GSI1SK?: string; // First GSI sort key
EntityType: string;
// ... entity-specific attributes
}typescript
// 替代实体特定的键:
// userId, orderId, productId
// 使用适用于所有实体的通用键:
interface BaseItem {
PK: string; // Partition Key
SK: string; // Sort Key
GSI1PK?: string; // First GSI partition key
GSI1SK?: string; // First GSI sort key
EntityType: string;
// ... 实体特定属性
}Example: E-commerce Schema
示例:电商Schema
typescript
// Users
{ PK: 'USER#123', SK: 'PROFILE', EntityType: 'User', name: 'John', email: 'john@test.com' }
{ PK: 'USER#123', SK: 'ADDRESS#1', EntityType: 'Address', street: '123 Main', city: 'NYC' }
// Orders for user (1:N relationship)
{ PK: 'USER#123', SK: 'ORDER#2024-001', EntityType: 'Order', total: 99.99, status: 'shipped' }
{ PK: 'USER#123', SK: 'ORDER#2024-002', EntityType: 'Order', total: 49.99, status: 'pending' }
// Order details (query by order ID using GSI)
{ PK: 'USER#123', SK: 'ORDER#2024-001', GSI1PK: 'ORDER#2024-001', GSI1SK: 'ORDER', ... }
{ PK: 'ORDER#2024-001', SK: 'ITEM#1', GSI1PK: 'ORDER#2024-001', GSI1SK: 'ITEM#1', productId: 'PROD#456', qty: 2 }
// Products
{ PK: 'PROD#456', SK: 'PRODUCT', EntityType: 'Product', name: 'Widget', price: 29.99 }typescript
// 用户
{ PK: 'USER#123', SK: 'PROFILE', EntityType: 'User', name: 'John', email: 'john@test.com' }
{ PK: 'USER#123', SK: 'ADDRESS#1', EntityType: 'Address', street: '123 Main', city: 'NYC' }
// 用户订单(1:N关系)
{ PK: 'USER#123', SK: 'ORDER#2024-001', EntityType: 'Order', total: 99.99, status: 'shipped' }
{ PK: 'USER#123', SK: 'ORDER#2024-002', EntityType: 'Order', total: 49.99, status: 'pending' }
// 订单详情(通过GSI按订单ID查询)
{ PK: 'USER#123', SK: 'ORDER#2024-001', GSI1PK: 'ORDER#2024-001', GSI1SK: 'ORDER', ... }
{ PK: 'ORDER#2024-001', SK: 'ITEM#1', GSI1PK: 'ORDER#2024-001', GSI1SK: 'ITEM#1', productId: 'PROD#456', qty: 2 }
// 商品
{ PK: 'PROD#456', SK: 'PRODUCT', EntityType: 'Product', name: 'Widget', price: 29.99 }Access Patterns Covered
覆盖的访问模式
1. Get user profile → Query PK='USER#123', SK='PROFILE'
2. Get user with addresses → Query PK='USER#123', SK begins_with 'ADDRESS'
3. Get all user orders → Query PK='USER#123', SK begins_with 'ORDER'
4. Get order by ID → Query GSI1, PK='ORDER#2024-001'
5. Get order with items → Query GSI1, PK='ORDER#2024-001'
6. Get product details → Query PK='PROD#456', SK='PRODUCT'1. 获取用户信息 → 查询 PK='USER#123', SK='PROFILE'
2. 获取用户及其地址 → 查询 PK='USER#123', SK 以 'ADDRESS' 开头
3. 获取用户所有订单 → 查询 PK='USER#123', SK 以 'ORDER' 开头
4. 通过订单ID获取订单 → 查询 GSI1, PK='ORDER#2024-001'
5. 获取订单及其商品 → 查询 GSI1, PK='ORDER#2024-001'
6. 获取商品详情 → 查询 PK='PROD#456', SK='PRODUCT'SDK v3 Setup (TypeScript)
SDK v3 配置(TypeScript)
Install Dependencies
安装依赖
bash
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodbbash
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodbClient Configuration
客户端配置
typescript
// lib/dynamodb.ts
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
const client = new DynamoDBClient({
region: process.env.AWS_REGION || 'us-east-1',
// For local development with DynamoDB Local
...(process.env.DYNAMODB_LOCAL && {
endpoint: 'http://localhost:8000',
credentials: { accessKeyId: 'local', secretAccessKey: 'local' }
})
});
// Document client for simplified operations
export const docClient = DynamoDBDocumentClient.from(client, {
marshallOptions: {
removeUndefinedValues: true, // Important: match v2 behavior
convertClassInstanceToMap: true
},
unmarshallOptions: {
wrapNumbers: false
}
});
export const TABLE_NAME = process.env.DYNAMODB_TABLE || 'MyTable';typescript
// lib/dynamodb.ts
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
const client = new DynamoDBClient({
region: process.env.AWS_REGION || 'us-east-1',
// 本地开发时使用DynamoDB Local
...(process.env.DYNAMODB_LOCAL && {
endpoint: 'http://localhost:8000',
credentials: { accessKeyId: 'local', secretAccessKey: 'local' }
})
});
// 简化操作的文档客户端
export const docClient = DynamoDBDocumentClient.from(client, {
marshallOptions: {
removeUndefinedValues: true, // 重要:匹配v2版本行为
convertClassInstanceToMap: true
},
unmarshallOptions: {
wrapNumbers: false
}
});
export const TABLE_NAME = process.env.DYNAMODB_TABLE || 'MyTable';Type Definitions
类型定义
typescript
// types/dynamodb.ts
export interface BaseItem {
PK: string;
SK: string;
GSI1PK?: string;
GSI1SK?: string;
EntityType: string;
createdAt: string;
updatedAt: string;
}
export interface User extends BaseItem {
EntityType: 'User';
userId: string;
email: string;
name: string;
}
export interface Order extends BaseItem {
EntityType: 'Order';
orderId: string;
userId: string;
total: number;
status: 'pending' | 'paid' | 'shipped' | 'delivered';
}
// Key builders
export const keys = {
user: (userId: string) => ({
PK: `USER#${userId}`,
SK: 'PROFILE'
}),
userOrders: (userId: string) => ({
PK: `USER#${userId}`,
SKPrefix: 'ORDER#'
}),
order: (userId: string, orderId: string) => ({
PK: `USER#${userId}`,
SK: `ORDER#${orderId}`,
GSI1PK: `ORDER#${orderId}`,
GSI1SK: 'ORDER'
})
};typescript
// types/dynamodb.ts
export interface BaseItem {
PK: string;
SK: string;
GSI1PK?: string;
GSI1SK?: string;
EntityType: string;
createdAt: string;
updatedAt: string;
}
export interface User extends BaseItem {
EntityType: 'User';
userId: string;
email: string;
name: string;
}
export interface Order extends BaseItem {
EntityType: 'Order';
orderId: string;
userId: string;
total: number;
status: 'pending' | 'paid' | 'shipped' | 'delivered';
}
// 键构造器
export const keys = {
user: (userId: string) => ({
PK: `USER#${userId}`,
SK: 'PROFILE'
}),
userOrders: (userId: string) => ({
PK: `USER#${userId}`,
SKPrefix: 'ORDER#'
}),
order: (userId: string, orderId: string) => ({
PK: `USER#${userId}`,
SK: `ORDER#${orderId}`,
GSI1PK: `ORDER#${orderId}`,
GSI1SK: 'ORDER'
})
};CRUD Operations
CRUD 操作
Put Item (Create/Update)
写入条目(创建/更新)
typescript
import { PutCommand } from '@aws-sdk/lib-dynamodb';
import { docClient, TABLE_NAME } from './dynamodb';
import { User, keys } from './types';
async function createUser(userId: string, data: { email: string; name: string }): Promise<User> {
const now = new Date().toISOString();
const item: User = {
...keys.user(userId),
EntityType: 'User',
userId,
email: data.email,
name: data.name,
createdAt: now,
updatedAt: now
};
await docClient.send(new PutCommand({
TableName: TABLE_NAME,
Item: item,
ConditionExpression: 'attribute_not_exists(PK)' // Prevent overwrite
}));
return item;
}typescript
import { PutCommand } from '@aws-sdk/lib-dynamodb';
import { docClient, TABLE_NAME } from './dynamodb';
import { User, keys } from './types';
async function createUser(userId: string, data: { email: string; name: string }): Promise<User> {
const now = new Date().toISOString();
const item: User = {
...keys.user(userId),
EntityType: 'User',
userId,
email: data.email,
name: data.name,
createdAt: now,
updatedAt: now
};
await docClient.send(new PutCommand({
TableName: TABLE_NAME,
Item: item,
ConditionExpression: 'attribute_not_exists(PK)' // 防止覆盖
}));
return item;
}Get Item (Read)
获取条目(读取)
typescript
import { GetCommand } from '@aws-sdk/lib-dynamodb';
async function getUser(userId: string): Promise<User | null> {
const result = await docClient.send(new GetCommand({
TableName: TABLE_NAME,
Key: keys.user(userId)
}));
return (result.Item as User) || null;
}typescript
import { GetCommand } from '@aws-sdk/lib-dynamodb';
async function getUser(userId: string): Promise<User | null> {
const result = await docClient.send(new GetCommand({
TableName: TABLE_NAME,
Key: keys.user(userId)
}));
return (result.Item as User) || null;
}Query (List/Search)
查询(列表/搜索)
typescript
import { QueryCommand } from '@aws-sdk/lib-dynamodb';
// Get all orders for a user
async function getUserOrders(userId: string): Promise<Order[]> {
const result = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :sk)',
ExpressionAttributeValues: {
':pk': `USER#${userId}`,
':sk': 'ORDER#'
},
ScanIndexForward: false // Newest first
}));
return (result.Items as Order[]) || [];
}
// Query GSI by order ID
async function getOrderById(orderId: string): Promise<Order | null> {
const result = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk',
ExpressionAttributeValues: {
':pk': `ORDER#${orderId}`
}
}));
return (result.Items?.[0] as Order) || null;
}
// Paginated query
async function getUserOrdersPaginated(
userId: string,
pageSize: number = 20,
lastKey?: Record<string, any>
): Promise<{ items: Order[]; lastKey?: Record<string, any> }> {
const result = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :sk)',
ExpressionAttributeValues: {
':pk': `USER#${userId}`,
':sk': 'ORDER#'
},
Limit: pageSize,
ExclusiveStartKey: lastKey
}));
return {
items: (result.Items as Order[]) || [],
lastKey: result.LastEvaluatedKey
};
}typescript
import { QueryCommand } from '@aws-sdk/lib-dynamodb';
// 获取用户所有订单
async function getUserOrders(userId: string): Promise<Order[]> {
const result = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :sk)',
ExpressionAttributeValues: {
':pk': `USER#${userId}`,
':sk': 'ORDER#'
},
ScanIndexForward: false // 最新的在前
}));
return (result.Items as Order[]) || [];
}
// 通过GSI按订单ID查询
async function getOrderById(orderId: string): Promise<Order | null> {
const result = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk',
ExpressionAttributeValues: {
':pk': `ORDER#${orderId}`
}
}));
return (result.Items?.[0] as Order) || null;
}
// 分页查询
async function getUserOrdersPaginated(
userId: string,
pageSize: number = 20,
lastKey?: Record<string, any>
): Promise<{ items: Order[]; lastKey?: Record<string, any> }> {
const result = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :sk)',
ExpressionAttributeValues: {
':pk': `USER#${userId}`,
':sk': 'ORDER#'
},
Limit: pageSize,
ExclusiveStartKey: lastKey
}));
return {
items: (result.Items as Order[]) || [],
lastKey: result.LastEvaluatedKey
};
}Update Item
更新条目
typescript
import { UpdateCommand } from '@aws-sdk/lib-dynamodb';
async function updateUser(userId: string, updates: Partial<Pick<User, 'name' | 'email'>>): Promise<User> {
// Build update expression dynamically
const updateParts: string[] = ['#updatedAt = :updatedAt'];
const names: Record<string, string> = { '#updatedAt': 'updatedAt' };
const values: Record<string, any> = { ':updatedAt': new Date().toISOString() };
if (updates.name !== undefined) {
updateParts.push('#name = :name');
names['#name'] = 'name';
values[':name'] = updates.name;
}
if (updates.email !== undefined) {
updateParts.push('#email = :email');
names['#email'] = 'email';
values[':email'] = updates.email;
}
const result = await docClient.send(new UpdateCommand({
TableName: TABLE_NAME,
Key: keys.user(userId),
UpdateExpression: `SET ${updateParts.join(', ')}`,
ExpressionAttributeNames: names,
ExpressionAttributeValues: values,
ReturnValues: 'ALL_NEW',
ConditionExpression: 'attribute_exists(PK)' // Must exist
}));
return result.Attributes as User;
}
// Atomic counter increment
async function incrementOrderCount(userId: string): Promise<void> {
await docClient.send(new UpdateCommand({
TableName: TABLE_NAME,
Key: keys.user(userId),
UpdateExpression: 'SET orderCount = if_not_exists(orderCount, :zero) + :inc',
ExpressionAttributeValues: {
':zero': 0,
':inc': 1
}
}));
}typescript
import { UpdateCommand } from '@aws-sdk/lib-dynamodb';
async function updateUser(userId: string, updates: Partial<Pick<User, 'name' | 'email'>>): Promise<User> {
// 动态构建更新表达式
const updateParts: string[] = ['#updatedAt = :updatedAt'];
const names: Record<string, string> = { '#updatedAt': 'updatedAt' };
const values: Record<string, any> = { ':updatedAt': new Date().toISOString() };
if (updates.name !== undefined) {
updateParts.push('#name = :name');
names['#name'] = 'name';
values[':name'] = updates.name;
}
if (updates.email !== undefined) {
updateParts.push('#email = :email');
names['#email'] = 'email';
values[':email'] = updates.email;
}
const result = await docClient.send(new UpdateCommand({
TableName: TABLE_NAME,
Key: keys.user(userId),
UpdateExpression: `SET ${updateParts.join(', ')}`,
ExpressionAttributeNames: names,
ExpressionAttributeValues: values,
ReturnValues: 'ALL_NEW',
ConditionExpression: 'attribute_exists(PK)' // 条目必须存在
}));
return result.Attributes as User;
}
// 原子计数器递增
async function incrementOrderCount(userId: string): Promise<void> {
await docClient.send(new UpdateCommand({
TableName: TABLE_NAME,
Key: keys.user(userId),
UpdateExpression: 'SET orderCount = if_not_exists(orderCount, :zero) + :inc',
ExpressionAttributeValues: {
':zero': 0,
':inc': 1
}
}));
}Delete Item
删除条目
typescript
import { DeleteCommand } from '@aws-sdk/lib-dynamodb';
async function deleteUser(userId: string): Promise<void> {
await docClient.send(new DeleteCommand({
TableName: TABLE_NAME,
Key: keys.user(userId),
ConditionExpression: 'attribute_exists(PK)'
}));
}typescript
import { DeleteCommand } from '@aws-sdk/lib-dynamodb';
async function deleteUser(userId: string): Promise<void> {
await docClient.send(new DeleteCommand({
TableName: TABLE_NAME,
Key: keys.user(userId),
ConditionExpression: 'attribute_exists(PK)'
}));
}Batch Operations
批量操作
Batch Write (Up to 25 items)
批量写入(最多25条条目)
typescript
import { BatchWriteCommand } from '@aws-sdk/lib-dynamodb';
async function batchCreateItems(items: BaseItem[]): Promise<void> {
// DynamoDB allows max 25 items per batch
const chunks = [];
for (let i = 0; i < items.length; i += 25) {
chunks.push(items.slice(i, i + 25));
}
for (const chunk of chunks) {
await docClient.send(new BatchWriteCommand({
RequestItems: {
[TABLE_NAME]: chunk.map(item => ({
PutRequest: { Item: item }
}))
}
}));
}
}typescript
import { BatchWriteCommand } from '@aws-sdk/lib-dynamodb';
async function batchCreateItems(items: BaseItem[]): Promise<void> {
// DynamoDB 每批最多允许25条条目
const chunks = [];
for (let i = 0; i < items.length; i += 25) {
chunks.push(items.slice(i, i + 25));
}
for (const chunk of chunks) {
await docClient.send(new BatchWriteCommand({
RequestItems: {
[TABLE_NAME]: chunk.map(item => ({
PutRequest: { Item: item }
}))
}
}));
}
}Batch Get (Up to 100 items)
批量获取(最多100条条目)
typescript
import { BatchGetCommand } from '@aws-sdk/lib-dynamodb';
async function batchGetUsers(userIds: string[]): Promise<User[]> {
const result = await docClient.send(new BatchGetCommand({
RequestItems: {
[TABLE_NAME]: {
Keys: userIds.map(id => keys.user(id))
}
}
}));
return (result.Responses?.[TABLE_NAME] as User[]) || [];
}typescript
import { BatchGetCommand } from '@aws-sdk/lib-dynamodb';
async function batchGetUsers(userIds: string[]): Promise<User[]> {
const result = await docClient.send(new BatchGetCommand({
RequestItems: {
[TABLE_NAME]: {
Keys: userIds.map(id => keys.user(id))
}
}
}));
return (result.Responses?.[TABLE_NAME] as User[]) || [];
}Transactions
事务
TransactWrite (Atomic Multi-Item)
事务写入(原子多条目操作)
typescript
import { TransactWriteCommand } from '@aws-sdk/lib-dynamodb';
async function createOrderWithItems(
userId: string,
orderId: string,
orderData: { total: number },
items: { productId: string; quantity: number }[]
): Promise<void> {
const now = new Date().toISOString();
const transactItems = [
// Create order
{
Put: {
TableName: TABLE_NAME,
Item: {
...keys.order(userId, orderId),
EntityType: 'Order',
orderId,
userId,
total: orderData.total,
status: 'pending',
createdAt: now,
updatedAt: now
},
ConditionExpression: 'attribute_not_exists(PK)'
}
},
// Update user's order count
{
Update: {
TableName: TABLE_NAME,
Key: keys.user(userId),
UpdateExpression: 'SET orderCount = if_not_exists(orderCount, :zero) + :inc',
ExpressionAttributeValues: { ':zero': 0, ':inc': 1 }
}
},
// Add order items
...items.map((item, index) => ({
Put: {
TableName: TABLE_NAME,
Item: {
PK: `ORDER#${orderId}`,
SK: `ITEM#${index}`,
GSI1PK: `ORDER#${orderId}`,
GSI1SK: `ITEM#${index}`,
EntityType: 'OrderItem',
productId: item.productId,
quantity: item.quantity,
createdAt: now
}
}
}))
];
await docClient.send(new TransactWriteCommand({
TransactItems: transactItems
}));
}typescript
import { TransactWriteCommand } from '@aws-sdk/lib-dynamodb';
async function createOrderWithItems(
userId: string,
orderId: string,
orderData: { total: number },
items: { productId: string; quantity: number }[]
): Promise<void> {
const now = new Date().toISOString();
const transactItems = [
// 创建订单
{
Put: {
TableName: TABLE_NAME,
Item: {
...keys.order(userId, orderId),
EntityType: 'Order',
orderId,
userId,
total: orderData.total,
status: 'pending',
createdAt: now,
updatedAt: now
},
ConditionExpression: 'attribute_not_exists(PK)'
}
},
// 更新用户的订单数量
{
Update: {
TableName: TABLE_NAME,
Key: keys.user(userId),
UpdateExpression: 'SET orderCount = if_not_exists(orderCount, :zero) + :inc',
ExpressionAttributeValues: { ':zero': 0, ':inc': 1 }
}
},
// 添加订单项
...items.map((item, index) => ({
Put: {
TableName: TABLE_NAME,
Item: {
PK: `ORDER#${orderId}`,
SK: `ITEM#${index}`,
GSI1PK: `ORDER#${orderId}`,
GSI1SK: `ITEM#${index}`,
EntityType: 'OrderItem',
productId: item.productId,
quantity: item.quantity,
createdAt: now
}
}
}))
];
await docClient.send(new TransactWriteCommand({
TransactItems: transactItems
}));
}GSI Patterns
GSI 模式
Sparse Index
稀疏索引
typescript
// Only items with GSI1PK attribute appear in the index
// Useful for "featured" or "flagged" items
// Featured products (only some products have GSI1PK)
{ PK: 'PROD#1', SK: 'PRODUCT', GSI1PK: 'FEATURED', GSI1SK: 'PROD#1', ... } // In index
{ PK: 'PROD#2', SK: 'PRODUCT', ... } // Not in index (no GSI1PK)
// Query featured products
const featured = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk',
ExpressionAttributeValues: { ':pk': 'FEATURED' }
}));typescript
// 只有包含GSI1PK属性的条目才会出现在索引中
// 适用于“精选”或“标记”条目
// 精选商品(仅部分商品包含GSI1PK)
{ PK: 'PROD#1', SK: 'PRODUCT', GSI1PK: 'FEATURED', GSI1SK: 'PROD#1', ... } // 在索引中
{ PK: 'PROD#2', SK: 'PRODUCT', ... } // 不在索引中(无GSI1PK)
// 查询精选商品
const featured = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk',
ExpressionAttributeValues: { ':pk': 'FEATURED' }
}));Inverted Index (GSI)
反向索引(GSI)
typescript
// Main table: User -> Orders (PK=USER#, SK=ORDER#)
// GSI: Orders by status (GSI1PK=STATUS#, GSI1SK=ORDER#)
{ PK: 'USER#123', SK: 'ORDER#001', GSI1PK: 'STATUS#pending', GSI1SK: 'ORDER#001', ... }
{ PK: 'USER#456', SK: 'ORDER#002', GSI1PK: 'STATUS#shipped', GSI1SK: 'ORDER#002', ... }
// Get all pending orders across all users
const pending = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk',
ExpressionAttributeValues: { ':pk': 'STATUS#pending' }
}));typescript
// 主表:用户 → 订单(PK=USER#, SK=ORDER#)
// GSI:按状态排序的订单(GSI1PK=STATUS#, GSI1SK=ORDER#)
{ PK: 'USER#123', SK: 'ORDER#001', GSI1PK: 'STATUS#pending', GSI1SK: 'ORDER#001', ... }
{ PK: 'USER#456', SK: 'ORDER#002', GSI1PK: 'STATUS#shipped', GSI1SK: 'ORDER#002', ... }
// 获取所有用户的待处理订单
const pending = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk',
ExpressionAttributeValues: { ':pk': 'STATUS#pending' }
}));Multi-Attribute Composite Keys (Nov 2025+)
多属性复合键(2025年11月+)
typescript
// New feature: Up to 4 attributes per partition/sort key
// No more synthetic keys like "TOURNAMENT#WINTER2024#REGION#NA-EAST"
// Table definition (IaC)
const table = {
AttributeDefinitions: [
{ AttributeName: 'tournament', AttributeType: 'S' },
{ AttributeName: 'region', AttributeType: 'S' },
{ AttributeName: 'score', AttributeType: 'N' }
],
GlobalSecondaryIndexes: [{
IndexName: 'TournamentRegionIndex',
KeySchema: [
{ AttributeName: 'tournament', KeyType: 'HASH' }, // Composite PK part 1
{ AttributeName: 'region', KeyType: 'HASH' }, // Composite PK part 2
{ AttributeName: 'score', KeyType: 'RANGE' }
]
}]
};typescript
// 新功能:每个分区/排序键最多支持4个属性
// 无需再使用类似 "TOURNAMENT#WINTER2024#REGION#NA-EAST" 的合成键
// 表定义(基础设施即代码)
const table = {
AttributeDefinitions: [
{ AttributeName: 'tournament', AttributeType: 'S' },
{ AttributeName: 'region', AttributeType: 'S' },
{ AttributeName: 'score', AttributeType: 'N' }
],
GlobalSecondaryIndexes: [{
IndexName: 'TournamentRegionIndex',
KeySchema: [
{ AttributeName: 'tournament', KeyType: 'HASH' }, // 复合PK的第1部分
{ AttributeName: 'region', KeyType: 'HASH' }, // 复合PK的第2部分
{ AttributeName: 'score', KeyType: 'RANGE' }
]
}]
};Python (boto3)
Python(boto3)
Setup
配置
python
undefinedpython
undefinedrequirements.txt
requirements.txt
boto3>=1.34.0
boto3>=1.34.0
db.py
db.py
import boto3
from boto3.dynamodb.conditions import Key, Attr
import os
dynamodb = boto3.resource(
'dynamodb',
region_name=os.getenv('AWS_REGION', 'us-east-1'),
endpoint_url=os.getenv('DYNAMODB_LOCAL_ENDPOINT') # For local dev
)
table = dynamodb.Table(os.getenv('DYNAMODB_TABLE', 'MyTable'))
undefinedimport boto3
from boto3.dynamodb.conditions import Key, Attr
import os
dynamodb = boto3.resource(
'dynamodb',
region_name=os.getenv('AWS_REGION', 'us-east-1'),
endpoint_url=os.getenv('DYNAMODB_LOCAL_ENDPOINT') # 本地开发使用
)
table = dynamodb.Table(os.getenv('DYNAMODB_TABLE', 'MyTable'))
undefinedOperations
操作
python
from datetime import datetime
from typing import Optional, List
from decimal import Decimal
def create_user(user_id: str, email: str, name: str) -> dict:
now = datetime.utcnow().isoformat()
item = {
'PK': f'USER#{user_id}',
'SK': 'PROFILE',
'EntityType': 'User',
'userId': user_id,
'email': email,
'name': name,
'createdAt': now,
'updatedAt': now
}
table.put_item(
Item=item,
ConditionExpression='attribute_not_exists(PK)'
)
return item
def get_user(user_id: str) -> Optional[dict]:
response = table.get_item(
Key={'PK': f'USER#{user_id}', 'SK': 'PROFILE'}
)
return response.get('Item')
def get_user_orders(user_id: str) -> List[dict]:
response = table.query(
KeyConditionExpression=Key('PK').eq(f'USER#{user_id}') & Key('SK').begins_with('ORDER#'),
ScanIndexForward=False
)
return response.get('Items', [])
def update_user(user_id: str, **updates) -> dict:
update_parts = ['#updatedAt = :updatedAt']
names = {'#updatedAt': 'updatedAt'}
values = {':updatedAt': datetime.utcnow().isoformat()}
for key, value in updates.items():
update_parts.append(f'#{key} = :{key}')
names[f'#{key}'] = key
values[f':{key}'] = value
response = table.update_item(
Key={'PK': f'USER#{user_id}', 'SK': 'PROFILE'},
UpdateExpression=f'SET {", ".join(update_parts)}',
ExpressionAttributeNames=names,
ExpressionAttributeValues=values,
ReturnValues='ALL_NEW'
)
return response['Attributes']
def delete_user(user_id: str) -> None:
table.delete_item(
Key={'PK': f'USER#{user_id}', 'SK': 'PROFILE'}
)python
from datetime import datetime
from typing import Optional, List
from decimal import Decimal
def create_user(user_id: str, email: str, name: str) -> dict:
now = datetime.utcnow().isoformat()
item = {
'PK': f'USER#{user_id}',
'SK': 'PROFILE',
'EntityType': 'User',
'userId': user_id,
'email': email,
'name': name,
'createdAt': now,
'updatedAt': now
}
table.put_item(
Item=item,
ConditionExpression='attribute_not_exists(PK)'
)
return item
def get_user(user_id: str) -> Optional[dict]:
response = table.get_item(
Key={'PK': f'USER#{user_id}', 'SK': 'PROFILE'}
)
return response.get('Item')
def get_user_orders(user_id: str) -> List[dict]:
response = table.query(
KeyConditionExpression=Key('PK').eq(f'USER#{user_id}') & Key('SK').begins_with('ORDER#'),
ScanIndexForward=False
)
return response.get('Items', [])
def update_user(user_id: str, **updates) -> dict:
update_parts = ['#updatedAt = :updatedAt']
names = {'#updatedAt': 'updatedAt'}
values = {':updatedAt': datetime.utcnow().isoformat()}
for key, value in updates.items():
update_parts.append(f'#{key} = :{key}')
names[f'#{key}'] = key
values[f':{key}'] = value
response = table.update_item(
Key={'PK': f'USER#{user_id}', 'SK': 'PROFILE'},
UpdateExpression=f'SET {", ".join(update_parts)}',
ExpressionAttributeNames=names,
ExpressionAttributeValues=values,
ReturnValues='ALL_NEW'
)
return response['Attributes']
def delete_user(user_id: str) -> None:
table.delete_item(
Key={'PK': f'USER#{user_id}', 'SK': 'PROFILE'}
)Local Development
本地开发
DynamoDB Local
DynamoDB 本地版
bash
undefinedbash
undefinedDocker
Docker方式
docker run -d -p 8000:8000 amazon/dynamodb-local
docker run -d -p 8000:8000 amazon/dynamodb-local
Create table locally
本地创建表
aws dynamodb create-table
--endpoint-url http://localhost:8000
--table-name MyTable
--attribute-definitions
AttributeName=PK,AttributeType=S
AttributeName=SK,AttributeType=S
AttributeName=GSI1PK,AttributeType=S
AttributeName=GSI1SK,AttributeType=S
--key-schema
AttributeName=PK,KeyType=HASH
AttributeName=SK,KeyType=RANGE
--global-secondary-indexes
'IndexName=GSI1,KeySchema=[{AttributeName=GSI1PK,KeyType=HASH},{AttributeName=GSI1SK,KeyType=RANGE}],Projection={ProjectionType=ALL}'
--billing-mode PAY_PER_REQUEST
--endpoint-url http://localhost:8000
--table-name MyTable
--attribute-definitions
AttributeName=PK,AttributeType=S
AttributeName=SK,AttributeType=S
AttributeName=GSI1PK,AttributeType=S
AttributeName=GSI1SK,AttributeType=S
--key-schema
AttributeName=PK,KeyType=HASH
AttributeName=SK,KeyType=RANGE
--global-secondary-indexes
'IndexName=GSI1,KeySchema=[{AttributeName=GSI1PK,KeyType=HASH},{AttributeName=GSI1SK,KeyType=RANGE}],Projection={ProjectionType=ALL}'
--billing-mode PAY_PER_REQUEST
undefinedaws dynamodb create-table
--endpoint-url http://localhost:8000
--table-name MyTable
--attribute-definitions
AttributeName=PK,AttributeType=S
AttributeName=SK,AttributeType=S
AttributeName=GSI1PK,AttributeType=S
AttributeName=GSI1SK,AttributeType=S
--key-schema
AttributeName=PK,KeyType=HASH
AttributeName=SK,KeyType=RANGE
--global-secondary-indexes
'IndexName=GSI1,KeySchema=[{AttributeName=GSI1PK,KeyType=HASH},{AttributeName=GSI1SK,KeyType=RANGE}],Projection={ProjectionType=ALL}'
--billing-mode PAY_PER_REQUEST
--endpoint-url http://localhost:8000
--table-name MyTable
--attribute-definitions
AttributeName=PK,AttributeType=S
AttributeName=SK,AttributeType=S
AttributeName=GSI1PK,AttributeType=S
AttributeName=GSI1SK,AttributeType=S
--key-schema
AttributeName=PK,KeyType=HASH
AttributeName=SK,KeyType=RANGE
--global-secondary-indexes
'IndexName=GSI1,KeySchema=[{AttributeName=GSI1PK,KeyType=HASH},{AttributeName=GSI1SK,KeyType=RANGE}],Projection={ProjectionType=ALL}'
--billing-mode PAY_PER_REQUEST
undefinedNoSQL Workbench
NoSQL Workbench
AWS provides NoSQL Workbench for visual data modeling and querying.
AWS 提供 NoSQL Workbench 用于可视化数据建模与查询。
CLI Quick Reference
CLI 快速参考
bash
undefinedbash
undefinedTable operations
表操作
aws dynamodb create-table --cli-input-json file://table.json
aws dynamodb describe-table --table-name MyTable
aws dynamodb delete-table --table-name MyTable
aws dynamodb create-table --cli-input-json file://table.json
aws dynamodb describe-table --table-name MyTable
aws dynamodb delete-table --table-name MyTable
Item operations
条目操作
aws dynamodb put-item --table-name MyTable --item '{"PK":{"S":"USER#1"},"SK":{"S":"PROFILE"}}'
aws dynamodb get-item --table-name MyTable --key '{"PK":{"S":"USER#1"},"SK":{"S":"PROFILE"}}'
aws dynamodb delete-item --table-name MyTable --key '{"PK":{"S":"USER#1"},"SK":{"S":"PROFILE"}}'
aws dynamodb put-item --table-name MyTable --item '{"PK":{"S":"USER#1"},"SK":{"S":"PROFILE"}}'
aws dynamodb get-item --table-name MyTable --key '{"PK":{"S":"USER#1"},"SK":{"S":"PROFILE"}}'
aws dynamodb delete-item --table-name MyTable --key '{"PK":{"S":"USER#1"},"SK":{"S":"PROFILE"}}'
Query
查询
aws dynamodb query --table-name MyTable
--key-condition-expression "PK = :pk"
--expression-attribute-values '{":pk":{"S":"USER#1"}}'
--key-condition-expression "PK = :pk"
--expression-attribute-values '{":pk":{"S":"USER#1"}}'
aws dynamodb query --table-name MyTable
--key-condition-expression "PK = :pk"
--expression-attribute-values '{":pk":{"S":"USER#1"}}'
--key-condition-expression "PK = :pk"
--expression-attribute-values '{":pk":{"S":"USER#1"}}'
Scan (avoid in production)
扫描(生产环境避免使用)
aws dynamodb scan --table-name MyTable --limit 10
---aws dynamodb scan --table-name MyTable --limit 10
---Anti-Patterns
反模式
- Scan operations - Always use Query with proper key conditions
- Hot partitions - Distribute writes with high-cardinality partition keys
- Large items - Keep items under 400KB; use S3 for large data
- Too many GSIs - Each GSI duplicates data; design carefully
- Ignoring capacity - Monitor consumed capacity, use on-demand for variable loads
- No condition expressions - Always validate with ConditionExpression
- Fetching all attributes - Use ProjectionExpression to limit data
- Multi-table design without reason - Single-table is preferred unless access patterns don't overlap
- 扫描操作 - 始终使用带合适键条件的查询(Query)
- 热点分区 - 使用高基数分区键分散写入
- 大条目 - 保持条目大小在400KB以下;大数据使用S3存储
- 过多GSI - 每个GSI都会复制数据;需谨慎设计
- 忽略容量规划 - 监控已消耗容量,按需模式适用于负载变化的场景
- 无条件表达式 - 始终使用ConditionExpression进行验证
- 获取所有属性 - 使用ProjectionExpression限制返回数据
- 无理由的多表设计 - 除非访问模式不重叠,否则优先选择单表设计