引言 🌟
在 AWS 云服务的众多产品中,DynamoDB 作为一款全托管的 NoSQL 数据库服务,以其高性能、可扩展性和灵活性赢得了众多开发者的青睐。然而,对于初学者来说,DynamoDB 的一些概念可能不太容易理解,尤其是二级索引这一重要特性。本文将深入浅出地讲解 DynamoDB 的二级索引,帮助你更好地设计和优化你的数据库结构。
DynamoDB 主键回顾 📝
在讨论二级索引之前,我们需要先回顾一下 DynamoDB 的主键概念。DynamoDB 表的主键可以是:
简单主键:仅包含一个分区键(Partition Key)
复合主键:包含分区键和排序键(Sort Key)
默认情况下,DynamoDB 只能通过主键高效地查询数据。例如,如果你的表使用 userId
作为分区键,orderId
作为排序键,你可以轻松地:
查询特定用户的所有订单
查询特定用户的特定订单
但如果你想按订单日期、金额或状态查询,就会面临挑战。这时,二级索引就派上用场了!🎯
什么是二级索引?🤔
二级索引是 DynamoDB 表的一个强大功能,它允许你使用主键之外的属性来查询数据。简单来说,二级索引就像是为你的数据创建了另一种视角,让你可以从不同的角度高效地访问数据。
DynamoDB 提供了两种类型的二级索引:
全局二级索引 (GSI) 🌍
本地二级索引 (LSI) 🏠
全局二级索引 (GSI) 详解 🌍
特点
可以有与主表完全不同的分区键和排序键
可以在表创建后添加或删除
有自己独立的预置吞吐量设置
只支持最终一致性读取
每个表最多可以有 20 个 GSI
实际案例
假设我们有一个电子商务应用的订单表,主键结构如下:
{
"userId": "user123",
"orderId": "order456",
"orderDate": "2023-07-15",
"orderAmount": 99.99,
"orderStatus": "SHIPPED",
"productId": "prod789",
"shippingAddress": "123 Main St"
}
如果我们想按订单状态和日期查询,可以创建一个 GSI:
{
"IndexName": "OrderStatus-OrderDate-index",
"KeySchema": [
{ "AttributeName": "orderStatus", "KeyType": "HASH" },
{ "AttributeName": "orderDate", "KeyType": "RANGE" }
],
"Projection": {
"ProjectionType": "ALL"
}
}
现在,我们可以轻松查询所有"已发货"的订单,并按日期排序:
response = table.query(
IndexName='OrderStatus-OrderDate-index',
KeyConditionExpression='orderStatus = :status AND orderDate BETWEEN :start AND :end',
ExpressionAttributeValues={
':status': 'SHIPPED',
':start': '2023-07-01',
':end': '2023-07-31'
}
)
这样,我们就能快速找到七月份所有已发货的订单!🚚
本地二级索引 (LSI) 详解 🏠
特点
必须使用与主表相同的分区键,但可以有不同的排序键
只能在表创建时定义,之后不能修改或删除
与主表共享预置吞吐量
支持强一致性读取和最终一致性读取
每个表最多可以有 5 个 LSI
实际案例
继续使用上面的订单表例子,如果我们想查询特定用户的订单,并按金额排序,可以创建一个 LSI:
{
"IndexName": "UserId-OrderAmount-index",
"KeySchema": [
{ "AttributeName": "userId", "KeyType": "HASH" },
{ "AttributeName": "orderAmount", "KeyType": "RANGE" }
],
"Projection": {
"ProjectionType": "INCLUDE",
"NonKeyAttributes": ["orderId", "orderDate", "orderStatus"]
}
}
现在,我们可以查询用户的所有订单,按金额从高到低排序:
response = table.query(
IndexName='UserId-OrderAmount-index',
KeyConditionExpression='userId = :uid',
ExpressionAttributeValues={
':uid': 'user123'
},
ScanIndexForward=False # 降序排列
)
这样,我们就能快速找到用户的高价值订单!💰
索引投影 - 优化成本和性能的关键 📊
创建索引时,你需要决定将哪些属性"投影"到索引中。DynamoDB 提供了三种投影类型:
KEYS_ONLY:只包含表和索引的键属性
INCLUDE:包含键属性和你指定的其他非键属性
ALL:包含表中的所有属性
投影类型会影响:
存储成本:投影的属性越多,存储成本越高
查询效率:如果需要的属性没有被投影,DynamoDB 需要额外查询主表
选择合适的投影类型是平衡成本和性能的关键!⚖️
# 创建一个只投影特定属性的 GSI
gsi = {
'IndexName': 'ProductId-index',
'KeySchema': [
{'AttributeName': 'productId', 'KeyType': 'HASH'},
],
'Projection': {
'ProjectionType': 'INCLUDE',
'NonKeyAttributes': ['orderDate', 'orderStatus']
}
}
GSI vs LSI:如何选择?🤷♂️
选择使用 GSI 还是 LSI 取决于你的具体需求:
何时选择 GSI 🌍
需要在表创建后灵活添加索引
需要使用与主表不同的分区键
需要查询跨多个分区的数据
可以接受最终一致性
何时选择 LSI 🏠
需要强一致性读取
查询限制在单个分区内
已经在表创建时规划好了所有查询模式
实际应用场景 💼
1. 社交媒体应用 👥
假设你正在构建一个社交媒体应用,用户可以发布帖子:
{
"userId": "user123",
"postId": "post456",
"postTimestamp": "2023-07-15T14:30:00Z",
"content": "Hello world!",
"likes": 42,
"tags": ["tech", "aws", "dynamodb"]
}
你可能需要的索引:
用户时间线 GSI:按
userId
(分区键) 和postTimestamp
(排序键) 查询用户的所有帖子热门帖子 GSI:按
likes
(分区键) 查询点赞数最多的帖子标签搜索 GSI:按
tags
(分区键) 查询带有特定标签的帖子
2. 游戏排行榜 🎮
对于一个在线游戏的排行榜系统:
{
"gameId": "game123",
"userId": "player456",
"score": 9500,
"level": 42,
"lastPlayed": "2023-07-15T20:45:00Z"
}
可能的索引:
游戏排行榜 GSI:按
gameId
(分区键) 和score
(排序键) 查询特定游戏的排行榜活跃玩家 GSI:按
lastPlayed
(分区键) 查询最近活跃的玩家
性能和成本优化技巧 💡
选择合适的投影类型:只投影你需要的属性,减少存储成本
使用稀疏索引:创建只包含部分项目的索引,减少索引大小
监控和调整:使用 CloudWatch 监控索引使用情况,及时调整
考虑写入放大:每次更新主表中的索引项时,也需要更新所有包含该项的索引
# 创建稀疏索引的例子
sparse_index = {
'IndexName': 'PremiumCustomer-index',
'KeySchema': [
{'AttributeName': 'isPremium', 'KeyType': 'HASH'},
{'AttributeName': 'userId', 'KeyType': 'RANGE'}
],
'Projection': {
'ProjectionType': 'ALL'
}
}
在这个例子中,只有包含 isPremium
属性的项目才会出现在索引中,从而减少索引大小。
常见陷阱和解决方案 ⚠️
GSI 吞吐量不足:GSI 有自己的吞吐量设置,确保为高流量索引分配足够的容量
项目大小限制:DynamoDB 项目大小限制为 400KB,包括所有索引
属性类型不匹配:确保索引键的数据类型与表中定义的一致
索引数量限制:每个表最多 20 个 GSI 和 5 个 LSI,提前规划好查询模式
结论 🎯
DynamoDB 的二级索引是一个强大的功能,它极大地扩展了 DynamoDB 的查询能力。通过合理设计和使用二级索引,你可以构建高性能、可扩展的应用程序,同时保持 NoSQL 数据库的灵活性。
记住,索引设计是数据建模的关键部分,应该在应用程序设计的早期就考虑进去。通过理解 GSI 和 LSI 的特点和限制,你可以为你的应用选择最合适的索引类型,优化查询性能,同时控制成本。
希望这篇文章能帮助你更好地理解和使用 DynamoDB 的二级索引!如果你有任何问题或想法,欢迎在评论区留言讨论。🙌