DynamoDB虽然不支持SQL里的LIKE模糊查询,但提供了begins_with函数来实现前缀匹配,这是官方推荐的标准做法。不过很多开发者第一次使用时都会踩坑:为什么分区键上不能用begins_with?为什么占位符写法会报错?本文围绕这几个核心问题,把begins_with的用法彻底讲清楚。

一、begins_with的基本语法与使用位置
begins_with是一个DynamoDB内置函数,作用是判断某个属性的值是否以指定前缀开头,返回布尔值。它只能用在两个地方:KeyConditionExpression(键条件表达式)和FilterExpression(过滤表达式)。
用在KeyConditionExpression中是最常见的场景,但有一个硬性限制:只能作用在排序键(Sort Key)上,绝对不能作用在分区键(Partition Key)上。这是因为分区键的定位必须精确,DynamoDB依靠分区键的哈希值找到数据所在的物理分区,模糊匹配会让它无法定位分区。如果你在分区键上使用begins_with,会直接收到ValidationException错误。
基本语法如下:
// KeyConditionExpression 标准写法
const params = {
TableName: "Orders",
KeyConditionExpression: "#pk = :pk AND begins_with(#sk, :prefix)",
ExpressionAttributeNames: {
"#pk": "customerId", // 分区键
"#sk": "orderDate" // 排序键
},
ExpressionAttributeValues: {
":pk": { S: "customer-1001" },
":prefix": { S: "2024-06" } // 匹配2024年6月的所有订单
}
};这里的#sk和:prefix都是占位符。属性名用#name形式映射到ExpressionAttributeNames,值用:value形式映射到ExpressionAttributeValues。begins_with的两个参数都必须通过占位符传入,不能把前缀字符串直接写进表达式里,否则会被当作属性名解析而报错。
二、排序键的复合前缀设计技巧
begins_with真正的威力在于配合复合排序键设计。DynamoDB允许一个表只有一个排序键,但你可以把多个业务维度拼接成一个排序键,从而实现多维度的前缀查询。
比如一个典型的订单表设计:分区键是customerId,排序键是orderDate#orderId这样的复合字符串。这样设计之后,你就可以用不同的前缀长度实现不同粒度的查询:只给日期就能查某天所有订单,给日期加状态前缀就能筛选特定状态的订单。
// 排序键格式:STATUS#2024-06-15#order123
// 查询某个客户所有"待支付"状态的订单
const params = {
TableName: "Orders",
KeyConditionExpression: "customerId = :cid AND begins_with(sk, :status)",
ExpressionAttributeValues: {
":cid": { S: "customer-1001" },
":status": { S: "PENDING#" }
}
};需要注意的是,复合键的分隔符选择很重要。如果用日期加数字拼接,字典序和数值序可能不一致,建议日期统一用yyyy-MM-dd这种定长格式,数字部分做零填充处理,否则前缀匹配出来的结果顺序会不符合预期。
另外一种常见做法是版本前缀,比如排序键写成v2#数据主体,版本切换时直接用begins_with(sk, :v2)过滤出新版本数据,旧数据慢慢清理,这是灰度迁移数据的常用手段。
三、KeyConditionExpression与FilterExpression中的区别
同样是用begins_with,放在键条件表达式和过滤表达式中的行为完全不同,理解这个差异对性能至关重要。
放在KeyConditionExpression中时,查询直接作用于排序键。DynamoDB存储时同一分区内的数据按排序键有序排列,前缀匹配本质上是一个范围扫描,效率很高,而且返回结果按排序键有序排列,天然支持分页游标。
而放在FilterExpression中时,情况就完全不同了。过滤表达式是在读取数据之后应用的,DynamoDB会先把符合条件的所有项目读出来(最多读1MB),再在结果上执行过滤。这意味着两件事:第一,过滤掉的数据同样消耗读取容量单位(RCU);第二,过滤后可能返回空结果但仍有消耗。
// 写法一:作用于排序键(推荐,高效) KeyConditionExpression: "pk = :pk AND begins_with(sk, :prefix)" // 写法二:作用于非键属性(低效,先读后过滤) FilterExpression: "begins_with(userName, :prefix)"
所以一个重要的设计原则是:如果前缀查询是高频操作,一定要把被查询的属性设计成排序键,或者为它建二级索引(GSI),把begins_with下推到键条件层面,而不是依赖FilterExpression做兜底过滤。
四、常见报错与注意事项
第一个高频报错是"Query key condition not supported",几乎都是因为在分区键上用了begins_with或者对排序键使用了不支持的函数。解决办法是重新设计查询:让分区键等值匹配,前缀条件留给排序键。如果确实需要按非键属性前缀查询,只能考虑建GSI或者用Scan加过滤(数据量大时不推荐)。
第二个坑是值类型不匹配。begins_with只支持字符串类型(S),如果排序键是数字类型,直接报错。所以设计表结构时就要想清楚,需要前缀查询的键必须是字符串。
第三个坑是前缀字符串里包含特殊字符。虽然DynamoDB本身对前缀值没有转义要求,但如果你用的SDK在拼装JSON时对某些字符处理不当,可能出现查询结果为空的情况,建议用SDK的占位符机制传值,不要手工拼接表达式。
最后是Java SDK用户的写法参考,展示一个完整的前缀查询:
QueryRequest request = QueryRequest.builder()
.tableName("Orders")
.keyConditionExpression("customerId = :cid AND begins_with(orderDate, :prefix)")
.expressionAttributeValues(Map.of(
":cid", AttributeValue.builder().s("customer-1001").build(),
":prefix", AttributeValue.builder().s("2024-06").build()
))
.build();
QueryResponse response = dynamoDbClient.query(request);总结一下,begins_with是DynamoDB里实现前缀匹配的唯一正道,用好它的关键在于表设计阶段就把查询模式想清楚:分区键负责精确定位,排序键通过复合设计承载前缀查询需求,让条件尽量落在KeyConditionExpression而不是FilterExpression上,这样才能兼顾功能和性能。
DynamoDBbegins_withKeyConditionExpression修改时间:2026-09-05 11:42:33