在数据库中,我们通常所说的“统计信息”实际上指的是优化器统计信息(optimizer statistics),它是一个描述数据库中表和列信息的数据集合。优化器代价模型(optimizer cost model)依赖于查询中涉及到的表、列、谓词等对象的统计信息来选取计划,它通常是代价模型选取最优执行计划的非常关键的部分。在 OceanBase 中,统计信息存储以普通数据的形式存储在内部表中,并且会在本地维护一个的统计信息缓存,以提高优化器对统计信息的访问速度。

统计信息的类型

在 OceanBase 中,统计信息有表统计信息(table level statistics)和列统计信息(column level statistics)两种。
表的统计信息主要存储在内部表 __all_meta_table 中,它包含了以下信息:

  • 表的行数

  • 表所占用的微块数

  • 表的平均行长

  • 表所占的空间大小

优化器可以利用表统计信息来优化计划的选择。除了非实体表以及统计信息内部表自身以外,所有的表都可以收集表统计信息。
以下是使用表统计信息进行计划选择的一个示例。

  1. 5-1
  2. create table t1 (c1 int, c2 int);
  3. create table t2 (d1 int key, d2 int);
  4. -- t1插入1000
  5. -- t2插入1000
  6. EXPLAIN select * from t1 left join t2 on t1.c1 = t2.d1;
  7. ========================================
  8. |ID|OPERATOR |NAME|EST. ROWS|COST|
  9. ----------------------------------------
  10. |0 |HASH OUTER JOIN| |1980 |8224|
  11. |1 | TABLE SCAN |t1 |1000 |499 |
  12. |2 | TABLE SCAN |t2 |1000 |499 |
  13. ========================================
  14. -- t1插入100
  15. -- t2插入100000
  16. EXPLAIN select /*+ use_hash(t1 t2) */ from t1 left join t2 on t1.c1 = t2.d1;
  17. ==============================================
  18. |ID|OPERATOR |NAME|EST. ROWS|COST |
  19. ----------------------------------------------
  20. |0 |HASH LEFT OUTER JOIN| |19800 |89496|
  21. |1 | TABLE SCAN |t1 |100 |61 |
  22. |2 | TABLE SCAN |t2 |100000 |40055|
  23. ==============================================
  24. -- 根据t2的表统计信息,HASH JOIN的代价在右表行数较大时代价较大,优化器选择了更优的计划,使用
  25. -- NESTED LOOP JOIN
  26. EXPLAIN select * from t1 left join t2 on t1.c1 = t2.d1;
  27. =====================================================
  28. |ID|OPERATOR |NAME|EST. ROWS|COST |
  29. -----------------------------------------------------
  30. |0 |NESTED-LOOP LEFT OUTER JOIN| |19800 |23649|
  31. |1 | TABLE SCAN |t1 |100 |61 |
  32. |2 | TABLE GET |t2 |198 |108 |
  33. =====================================================

列的统计信息存储在内部表 __all_column_statistic 中,它包含了以下信息:

  • 列中不同的值的数量(number of distinct values,NDV)

  • 列中 null 值的数量

  • 列的最大值最小值

优化器可以利用列的统计信息来优化的选择。除了非实体表以及统计信息内部表自身以外,所有的列都可以收集列统计信息。列的统计信息主要用来计算谓词、连接条件的选择率,从而更好地计算算子向上层吐出的行数。

以下是使用列统计信息来优化查询计划选择的一个例子。

  1. 5-2
  2. create table t1 (c1 int, c2 int);
  3. create table t2 (d1 int key, d2 int);
  4. -- t1插入200
  5. -- t2插入100000行,其中满足t2.d2 = 1的有50000
  6. -- 在没有列统计信息的情况下,优化器认为t2.d2 = 1是一个强过滤条件,满足该条件的约为198行,
  7. -- 所以选择以行数更少的表t2作为build table(建立hash表),t1probe table(扫描)
  8. EXPLAIN select * from t1 join t2 on c1 = d1 and d2 = 1;
  9. =========================================
  10. |ID|OPERATOR |NAME|EST. ROWS|COST |
  11. -----------------------------------------
  12. |0 |HASH INNER JOIN| |79 |63920|
  13. |1 | TABLE SCAN |t2 |198 |63417|
  14. |2 | TABLE SCAN |t1 |200 |121 |
  15. =========================================
  16. -- ALTER SYSTEM MAJOR FREEZE;
  17. -- 在有列统计信息的情况下,优化器可以察觉到右表吐出的行数信息,从而以小表t1build table
  18. EXPLAIN select * from t1 join t2 on c1 = d1 and d2 = 1;
  19. =========================================
  20. |ID|OPERATOR |NAME|EST. ROWS|COST |
  21. -----------------------------------------
  22. |0 |HASH INNER JOIN| |101 |62671|
  23. |1 | TABLE SCAN |t1 |200 |85 |
  24. |2 | TABLE SCAN |t2 |50000 |44543|
  25. =========================================

在大多数情况下,表和列的统计信息共同作用,优化器得以利用这些信息更精确地选择执行计划。

收集统计信息的时机

统计信息的收集是一个相对消耗时间的过程。在 OceanBase 中,主要的统计信息在进行一次大版本合并(major freeze)之后进行一次统一的收集。在这一阶段,存储层会收集所有表、所有列的统计信息,更新到内部表中。(列 NDV 的信息更新采用业内较为认可的近似算法 HyperLogLog 实现,它具有执行速度快、占用空间少的特点,并且理论的平均准确率期望可以达到 98% 以上。)

此外,由于 OceanBase 底层的存储由 MemTable 和 SSTable 构成,MemTable 中数据的统计信息也非常重要,除了统一的收集之外,目前还会对 MemTable 中的表的行数进行收集,这个收集的代价相对较小,它的收集分散到各个优化过程中,每一次优化器的优化都会收集这部分的信息。

需要说明的是,由于数据库并不是一直在收集统计信息,所以会存在部分表、部分行没有统计信息的情况。对于这类情况,OceanBase 会使用一个缺省的统计信息。

统计信息缓存

数据库收集的最新的统计信息存储在内部表中,而频繁地访问内部表代价较大,而统计信息的更新并不是那么频繁,而且一张表、一个列的统计信息可以被多个查询使用。

基于上述原因引入了统计信息缓存(optimizer statistic cache)机制,本地使用一个 KV 缓存结构将统计信息缓存在内存当中,当优化器需要访问统计信息时,首先查询本地的缓存,当缓存中没有相应的统计信息时,再向内部表查询。

统计信息查询

目前,sys 租户可以通过前述的内部表来查询表和列的统计信息,如下例所示,SQL 给出了一个使用内部表查询表、列的统计信息的示例。使用该 SQL 可以通过传入数据库名、表名和列名这三个信息的组合获取到包括表的行数、列的 NDV、列的 null 值数量以及列的最大最小值信息。

  1. -- 内部表使用16进制格式存储最大值最小值信息,使用des_hex_str()函数可以将它转化为可读的格式。
  2. select
  3. r_c as row_count,
  4. s.num_distinct as NDV,
  5. s.num_null as num_null,
  6. des_hex_str(s.min_value) as min,
  7. des_hex_str(s.max_value) as max
  8. from
  9. __all_column_statistic s,
  10. __all_database d,
  11. __all_table t,
  12. __all_column c,
  13. (select max(row_count)as r_c, table_id from __all_meta_table group by table_id) m
  14. where
  15. s.table_id= t.table_id
  16. and t.database_id= d.database_id
  17. and d.database_name= 'tpch_1g' -- 数据库名
  18. and t.table_name= 'orders' -- 表名
  19. and c.table_id= t.table_id
  20. and c.column_name= 'o_orderkey' -- 列名
  21. and s.column_id= c.column_id
  22. and s.column_id= c.column_id
  23. and s.table_id= m.table_id;
  24. -- 查询结果示例
  25. +-----------+---------+----------+------+---------+
  26. | row_count | NDV | num_null | min | max |
  27. +-----------+---------+----------+------+---------+
  28. | 1500000 | 1479709 | 0 | 1 | 6000000 |
  29. +-----------+---------+----------+------+---------+
  30. 1 row in set (0.14 sec)