系列专栏声明:比较流水,主要是写一些踩坑的点,和实践中与文档差距较大的地方的思考。这个专栏的典型特征可能是 次佳实践
,争取能在大量的最佳实践中生存。
本篇基本都是伪代码,因为原始的 TableStore SDK 设计得太不可读了,实际使用请自行查阅文档。
一、Primary Key = Partition Key + Sort Key
考虑 BookStore 的场景:City、District、Road、No 都是业务上实际存在的字段,我们也确实在使用这些字段的组合去记做唯一的 Id 标识,因此支持多字段组合避免了由业务层做 PK 拼接和拆解。
那么自然就推导出 2 个重要的特征:1)这些 Key 都是必填的;2)取值的时候将组合整体视作条件,即 WHERE CityDistrictRoadNo = ShanghaiPudong001003
,而不存在 WHERE District = Pudong
。
City | District | Road | No |
---|---|---|---|
Shanghai | Pudong | 001 | 003 |
Shanghai | Minhang | 001 | 004 |
Shenzhen | Nansha | 002 | 005 |
关于如何设计 Partition Key 和 Sort Key,建议参考:
二、GetSingleRow,获取单行数据
TableStore 支持 1-4 个 Key 组成 PK;但下面为了演示的方便,我们只使用 2 个 Key。
Row row = client.getSingleRow("BookStore",
new PrimaryKey("Shanghai", "Pudong"));
根据上面的介绍,我们应该已经习惯了 PK 的定义,如果在设计表的时候确认了要使用 2 个 Key 来组成 PK,那么这 2 个 Key 始终是必填的。
三、GetRangeRow,By PK 获取范围数据
考虑一个传统的 MySQL 设计,你需要先按照一定的排序规则将数据按顺序插入,然后搜索 WHERE 10 < Id AND Id < 20
。但在 TableStore 里你只管插入,会自动去排序和索引,所以搜索条件就变成了 WHERE Shanghai.Pudong < City.District AND City.District < Shenzhen.Nanshan
。
再次体会一下这里把多个 Key 当作一个整体 PK 来使用的感觉。不要去想 WHERE Shanghai < City < Shenzhen, Pudong < District < Nanshan
或它的变体,在概念上就不成立。
List<Row> rows = client.getRangeRow("BookStore",
new PrimaryKey("Shanghai", "Pudong"), // start
new PrimaryKey("Shenzhen", "Nanshan")); // end
搜索可以不设下限或上限,用系统提供的常量来表示:
List<Row> rows = client.getRangeRow("BookStore",
new PrimaryKey("Shanghai", INF_MIN), // start
new PrimaryKey("Shenzhen", INF_MAX)); // end
也可以使用一些更灵活的组合,但可读性就差很多了:
List<Row> rows = client.getRangeRow("BookStore",
new PrimaryKey("Shanghai", INF_MIN), // start
new PrimaryKey(INF_MAX, "Nanshan")); // end
排序的规则为从左到右依次比较 4 个 Key,如果第 Key 0 已经比较出了大小,就不再比较第 Key 1 / 2 / 3 了。参考这个例子 更详细直观一些。还有一些在扫描海量数据时的限制、翻页、或者流式读取,请参考官方的参数说明。本篇的重点在于帮你强化 多个 Key 其实是一个整体的 PK 这个概念。
四、Global Secondary Index / GSI / 全局二级索引
排序是从左到右的,只要筛选条件包含了了 Key 0,那么 Key 1 / 2 / 3 就不会生效;不包含 Key 0,那么 Key 1 才能生效;但有些场景下你确实需要按 Key 1 和 Key 0 组合搜索,且优先考虑 Key 1。因此需要建立一个 Key 1 优于 Key 0 的 GSI
,直观的看就是把 2 个 Key 的左右顺序对调一下。
District | City | Road | No |
---|---|---|---|
Pudong | Shanghai | 001 | 003 |
Minhang | Shanghai | 001 | 004 |
Nanshan | Shanghai | 002 | 005 |
// 原表:Shanghai.F 在这个搜索结果内
List<Row> rows = client.getRangeRow("BookStore",
new PrimaryKey("Shanghai", "A"), // start
new PrimaryKey("Shenzhen", "D")); // end
// GSI:优先排序 Key 1,所以 Shanghai.F 不在这个搜索结果内
List<Row> rows = client.search("BookStore", "Index2",
new PrimaryKey("A", "Shanghai"), // start
new PrimaryKey("D", "Shenzhen")); // end
GSI 必须包含原表的所有 PK。如果原表用了 4 个 PK,那么索引的 PK 只能是它们的排列组合。如果原表没有用满 4 个 PK,那么可以把预定义列也放到 PK 里来:
原表: [ PK0, SK1, SK2 ] C1, C2, C3
GSI: [ C1, PK0, SK1, SK2 ] C2, C3
或者 GSI: [ C1, SK1, PK0, SK2 ] C2, C3
GSI 本质上就是重新设计 Primary Key。
Local Secondary Index / LSI / 本地二级索引
则是保持原 Partition Key 的前提下重新设计 Sort Key。
这个例子讲得很详细:通过全局二级索引加速表格存储上的数据查询。
以及以上两者并不是用来做搜索的,主要的使用场景还是常规顺序取值,搜索应该用多元索引,见下。
五、Filter
Filter 可以按照单个 Key 筛选,既可以是 PK,也可以是 Attr Col。但扫描实际是按 PK 条件全表扫描的,Filter 只是减少了被返回的数据的量,即减少了网络传输的大小。Filter 也不是搜索,仍然应该按 PK 设计与扫描的思路去考虑。
List<Row> rows = client.getRangeRow("BookStore",
new PrimaryKey(INF_MIN, INF_MIN), // start
new PrimaryKey(INF_MAX, INF_MAX), // end
new Filter("District", "==", "Pudong")); // Filter by PK Col
List<Row> rows = client.getRangeRow("BookStore",
new PrimaryKey(INF_MIN, INF_MIN), // start
new PrimaryKey(INF_MAX, INF_MAX), // end
new Filter("Size", ">=", "700")); // Filter by Attr Col
以上演示的是 SingleColumnValueFilter,还有多重条件组合筛选 CompositeColumnValueFilter,等想到好的例子再来补充代码。
六、使用 SQL
好像没啥明确的意义,等想到好的例子再来补充。
也许优势是可以使用 AVG 等函数,不确定。
七、多元索引
按文档的宣传,是 无视 Partition Key 和 Sort Key 设计的高性能,可能是倒排?
按你正常写搜索的思路就好:
List<Row> rows = client.search("BookStore", "SearchIndex",
new Query("Road", "LIKE", "Hutai"),
AND,
new Query("Building", "LIKE", "Tower")); // 伪代码
值得注意的是 Nested 类型必须是数组:
// 错误的示例: Building: { Name: "Tower" }
List<Row> rows = client.search("BookStore", "SearchIndex",
new Query("Building.Name", "LIKE", "Tower"));
// Buildings: [ { Name: "Tower" }, { Name: "Plaza" } ]
List<Row> rows = client.search("BookStore", "SearchIndex",
new Query("Buildings.Name", "LIKE", "Tower"));
// Buildings 可能有很多栋楼,只要其中有一栋楼叫 Tower,就返回这个 Buildings 所在的整个行
// 显然该行也会包含 Plaza
这个例子很好,直接看吧:详解TableStore模糊查询——以订单场景为例。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。