当前位置:   article > 正文

MySQL 聚合函数初探

percent函数是聚合函数吗

MySQL 提供了许多聚合函数,常见的如sum,avg,count,min,max等。

那这些聚合函数在MySQL 底层是怎么实现的?

聚合函数(Aggregate Function)实现的大部分代码在item_sum.h和item_sum.cc。

聚合函数在代码中具体的枚举如下:

  1. enum Sumfunctype {
  2.     COUNT_FUNC,           // COUNT
  3.     COUNT_DISTINCT_FUNC,  // COUNT (DISTINCT)
  4.     SUM_FUNC,             // SUM
  5.     SUM_DISTINCT_FUNC,    // SUM (DISTINCT)
  6.     AVG_FUNC,             // AVG
  7.     AVG_DISTINCT_FUNC,    // AVG (DISTINCT)
  8.     MIN_FUNC,             // MIN
  9.     MAX_FUNC,             // MAX
  10.     STD_FUNC,             // STD/STDDEV/STDDEV_POP
  11.     VARIANCE_FUNC,        // VARIANCE/VAR_POP and VAR_SAMP
  12.     SUM_BIT_FUNC,         // BIT_AND, BIT_OR and BIT_XOR
  13.     UDF_SUM_FUNC,         // user defined functions
  14.     GROUP_CONCAT_FUNC,    // GROUP_CONCAT
  15.     JSON_AGG_FUNC,        // JSON_ARRAYAGG and JSON_OBJECTAGG
  16.     ROW_NUMBER_FUNC,      // Window functions
  17.     RANK_FUNC,
  18.     DENSE_RANK_FUNC,
  19.     CUME_DIST_FUNC,
  20.     PERCENT_RANK_FUNC,
  21.     NTILE_FUNC,
  22.     LEAD_LAG_FUNC,
  23.     FIRST_LAST_VALUE_FUNC,
  24.     NTH_VALUE_FUNC,
  25.     ROLLUP_SUM_SWITCHER_FUNC,
  26.     GEOMETRY_AGGREGATE_FUNC
  27.   };

本文以下列示例来讲解:

  1. CREATE TABLE test_agg (c1 int NULL)
  2. INSERT into test_agg values(1),(2),(3),(3),(4),(4),(5),(5),(5);
  3. SELECT count(DISTINCT c1) from test_agg;

聚合函数的类设计大概如下

3d35fbbedffbb073f8e2469f156224c5.jpeg


由上图可以发现MySQL 聚合函数实现是把distinct逻辑抽离出来,变成了aggregator_distinct和aggregator_simple,

服务于继承了Item_sum的所有聚合类。(当然Item_sum本身是继承于Item)

  1. class Aggregator_simple : public Aggregator {
  2.  public:
  3.   Aggregator_simple(Item_sum *sum) : Aggregator(sum) {}
  4.   Aggregator_type Aggrtype() override { return Aggregator::SIMPLE_AGGREGATOR; }
  5.   bool setup(THD *thd) override { return item_sum->setup(thd); }
  6.   void clear() override { item_sum->clear(); }
  7.   bool add() override { return item_sum->add(); }
  8.   void endup() override {}
  9. };
  10. class Aggregator_distinct : public Aggregator {
  11.  public:
  12.   ~Aggregator_distinct() override;
  13.   Aggregator_type Aggrtype() override { return DISTINCT_AGGREGATOR; }
  14.   bool setup(THD *) override;
  15.   void clear() override;
  16.   bool add() override;
  17.   void endup() override;
  18. };

上面是2个类的部分代码,由此我们发现 Aggregator_simple 基本只是个调用wrap,表示非distinct的Item_sum处理,

直接调用的是聚合类的逻辑。

在 MySQL 中要实现聚合函数要有3个重要的步骤:setup, add, endup。

  • setup 在处理之前初始化

  • add 表示每条记录的处理

  • endup 收尾后最后计算聚合的结果。

回到代码

setup 阶段

(Aggregator_distinct::setup 截取部分代码)

  1. if (!(table = create_tmp_table(thd, tmp_table_param, list, nullptr, true,
  2.                                    false, query_block->active_options(),
  3.                                    HA_POS_ERROR, "")))
  4. if (all_binary) {
  5.         cmp_arg = (void *)&tree_key_length;
  6.         compare_key = simple_raw_key_cmp;
  7. else {
  8.     if (table->s->fields == 1) {
  9.         compare_key = simple_str_key_cmp;
  10.         cmp_arg = (void *)table->field[0];
  11.     } else {
  12.         uint32 *length;
  13.         compare_key = composite_key_cmp;
  14.         ....
  15.      }
  16.   }
  17.  tree = new (thd->mem_root) Unique(compare_key, cmp_arg, tree_key_length,
  18.           item_sum->ram_limitation(thd));
  19.  if (!tree) return true;

由上知 setup 阶段主要做的是创建临时表和 tree ,设置比较函数。

add 阶段

(Aggregator_distinct::add 截取部分代码)

  1. if (tree) {
  2.       return tree->unique_add(table->record[0] + table->s->null_bytes);
  3.     }
  4.     if (!check_unique_constraint(table)) return false;
  5.     if ((error = table->file->ha_write_row(table->record[0])) &&
  6.         !table->file->is_ignorable_error(error))

这边看到当 tree 存在时,MySQL 把记录加入 tree (实际为红黑树)中来去重复。

如果tree不存在,就用临时表来持久存储。在 ha_write_row 写入临时表之前会 check_unique_constraint 去重。

而 tree 和临时表就是 setup 阶段所创建的。setup 的 tree 是存在,什么时候销毁了呢。看下面:

  1. inline bool unique_add(void *ptr) {
  2.     DBUG_TRACE;
  3.     DBUG_PRINT("info", ("tree %u - %lu", tree.elements_in_tree, max_elements));
  4.     if (tree.elements_in_tree > max_elements && flush()) return true;
  5.     return !tree_insert(&tree, ptr, 0, tree.custom_arg);
  6.   }
  7. bool Unique::flush() {
  8.   Merge_chunk file_ptr;
  9.   elements += tree.elements_in_tree;
  10.   file_ptr.set_rowcount(tree.elements_in_tree);
  11.   file_ptr.set_file_position(my_b_tell(&file));
  12.   if (tree_walk(&tree, unique_write_to_file, this, left_root_right) ||
  13.       file_ptrs.push_back(file_ptr))
  14.     return true/* purecov: inspected */
  15.   delete_tree(&tree);
  16.   return false;
  17. }

可以看到MySQL 的策略是维护一颗红黑树这样的数据结构来去重。

当tree的数量过大时,内存放不下,就会flush到磁盘上,采用临时表来持久化,同时销毁tree。

endup 阶段
  1. if (tree && tree->is_in_memory()) {
  2.       sum->count = (longlong)tree->elements_in_tree();
  3.       endup_done = true;
  4.     }
  5.     if (!tree) {
  6.       table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK);
  7.       if (table->file->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT)
  8.         sum->count = table->file->stats.records;
  9.       else {
  10.         if (table->file->inited) table->file->ha_index_or_rnd_end();
  11.         ha_rows num_rows = 0;
  12.         table->file->ha_records(&num_rows);
  13.         if (table->hash_field) table->file->ha_index_init(0false);
  14.         sum->count = static_cast<longlong>(num_rows);
  15.       }
  16.       endup_done = true;
  17.     }

可以看到最后取结果的时候

如果 tree 存在而且在内存中,直接取 tree 的节点个数。

如果 tree 不存在就取临时表的行数。

Enjoy GeatSQL :)


《深入浅出MGR》视频课程

戳此小程序即可直达B站

https://www.bilibili.com/medialist/play/1363850082?business=space_collection&business_id=343928&desc=0


文章推荐:


想看更多技术好文,点个“在看”吧!

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/933334
推荐阅读
相关标签
  

闽ICP备14008679号