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

    统计信息的类型

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

    • 表的行数

    • 表所占用的微块数

    • 表的平均行长

    • 表所占的空间大小

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

    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值的数量

    • 列的最大值最小值

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

    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)