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

统计信息的类型

在 OceanBase 数据库中,统计信息有表统计信息(Table Level Statistics)和列统计信息(Column Level Statistics)两种。表的统计信息主要存储在内部表 __all_meta_table 中,它包含了以下信息:

  • 表的行数

  • 表所占用的微块数

  • 表的平均行长

  • 表所占的空间大小

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

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

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

  • 列中不同的值的数量(Number of Distinct Values,NDV)

  • 列中 NULL 值的数量

  • 列的最大值最小值

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

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

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

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

收集统计信息的时机

统计信息的收集是一个相对消耗时间的过程。在 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)