Gocql操作Scylla时,ALLOW FILTERING分页无匹配记录返回空的问题
问题描述
使用gocql与Scylla数据库交互,通过page state实现分页时,添加ALLOW FILTERING后,若当前分页范围内无匹配过滤条件的记录,会返回空结果。
示例表结构:
CREATE TABLE table ( partitonKeyOne TEXT, partitonKeyTwo TEXT, clusteringKeyOne TEXT, filterColumnOne TEXT, PRIMARY KEY ((partitonKeyOne, partitonKeyTwo), clusteringKeyOne) ) WITH CLUSTERING ORDER BY (clusteringKeyOne DESC);
分页代码:
getQuery := "SELECT * FROM table WHERE partitonKeyOne = 'abc' AND partitonKeyTwo = 'def' AND filterColumnOne = 'xyz' ALLOW FILTERING;" queryResponse := db.GetSession().Query( getQuery, ).Consistency(gocql.One) queryResponse.PageSize(limit) queryResponse.PageState(pageState) iterator := queryResponse.Iter() nextPageState := hex.EncodeToString(iterator.PageState())
场景:表内共100条记录,每页大小设为20,其中60条匹配过滤条件。期望从这60条中分页获取,但实际若当前分页批次无匹配记录,会返回空结果。
问题原因
Scylla(及Cassandra)中ALLOW FILTERING的执行逻辑是:先按分页大小从分区中读取一批数据,再对这批数据应用过滤条件。如果读取的这批数据中没有符合过滤条件的记录,就会返回空结果,同时page state会直接推进到下一批数据的起始位置,导致跳过后续可能存在匹配记录的区域。
解决方案
1. 重构表结构(推荐)
避免使用ALLOW FILTERING,将过滤字段纳入聚类键,让查询可以直接利用有序的聚类键定位数据:
CREATE TABLE table ( partitonKeyOne TEXT, partitonKeyTwo TEXT, filterColumnOne TEXT, clusteringKeyOne TEXT, PRIMARY KEY ((partitonKeyOne, partitonKeyTwo), filterColumnOne, clusteringKeyOne) ) WITH CLUSTERING ORDER BY (filterColumnOne ASC, clusteringKeyOne DESC);
此时查询无需ALLOW FILTERING,且分页会直接从匹配filterColumnOne='xyz'的记录中按页读取:
getQuery := "SELECT * FROM table WHERE partitonKeyOne = 'abc' AND partitonKeyTwo = 'def' AND filterColumnOne = 'xyz';" // 后续分页逻辑保持不变
2. 应用层二次分页(无法改表时使用)
先查询所有匹配过滤条件的clusteringKeyOne,再分页通过clusteringKeyOne查询完整记录:
// 第一步:获取所有匹配的聚类键 keyQuery := "SELECT clusteringKeyOne FROM table WHERE partitonKeyOne = 'abc' AND partitonKeyTwo = 'def' AND filterColumnOne = 'xyz' ALLOW FILTERING;" var keys []string if err := db.GetSession().Query(keyQuery).Iter().ScanSlice(&keys); err != nil { // 处理错误 } // 第二步:对keys进行应用层分页 startIdx := page * limit endIdx := startIdx + limit if endIdx > len(keys) { endIdx = len(keys) } pageKeys := keys[startIdx:endIdx] // 第三步:查询当前页的完整记录 recordQuery := "SELECT * FROM table WHERE partitonKeyOne = 'abc' AND partitonKeyTwo = 'def' AND clusteringKeyOne IN ?;" iterator := db.GetSession().Query(recordQuery, pageKeys).Iter() // 处理结果
注意:若匹配记录量极大,存储所有clusteringKeyOne会占用较多内存,可对第一步的聚类键查询也进行分页(需处理多次查询的page state)。
3. 空页自动重试逻辑
当查询返回空结果且存在下一页page state时,自动重试查询,直到获取数据或确认无更多记录:
var results []YourRecordType for { iterator := queryResponse.Iter() var record YourRecordType found := false for iterator.Scan(&record.partitonKeyOne, &record.partitonKeyTwo, &record.clusteringKeyOne, &record.filterColumnOne) { results = append(results, record) found = true } nextPageState := hex.EncodeToString(iterator.PageState()) if err := iterator.Close(); err != nil { // 处理错误 break } if found || nextPageState == "" { break } // 空页,用当前nextPageState继续查询 queryResponse = db.GetSession().Query(getQuery).Consistency(gocql.One).PageSize(limit).PageState(nextPageState) }
此方法会增加查询次数,仅适合过滤后匹配记录分布较分散的场景。
内容的提问来源于stack exchange,提问作者N G
相关产品推荐
相关产品推荐

