【GreatSQL优化器-18】GROUP_INDEX_SKIP_SCAN

SpringBoot+Vue+MySQL 高校学科竞赛平台 完整可运行版(源码+文档) 本项目基于SpringBoot+Vue+MySQL构建高校学科竞赛管理平台,覆盖学生、教师、领队教师、管理员四大角色,实现竞赛发布、报名、刷题、评分、公示等全流程闭环管理。系统架构清晰,技术主流,配套完整源码、数据库脚本、万字论文、开题报告、答辩PPT及部署文档,支持一键运行,适用于毕业设计、课程设计及院系二次开发,真正实现“开箱即用”。 阅读详情

【GreatSQL优化器-18】GROUP_INDEX_SKIP_SCAN

一、GROUP_INDEX_SKIP_SCAN介绍

GreatSQL 优化器的分组索引跳跃扫描(GROUP Index Skip Scan) 是一种优化查询的技术,尤其在联合索引中用于减少扫描的无效行数。group by操作在没有合适的索引可用的时候,通常先扫描整个表提取数据并创建一个临时表,然后按照 group by 指定的列进行排序。在这个临时表里面,对于每一个group的数据行来说是连续在一起的。完成排序之后,就可以发现所有的groups,并可以执行聚集函数(aggregate function)。可以看到,在没有使用索引的时候,需要创建临时表和排序。在执行计划中通常可以看到“Using temporary; Using filesort”

GROUP索引跳跃扫描利用的是联合索引中非首列(非最左前缀)的索引列,来提高查询效率。例如,假如有个查询需求,需要查某个列的 distinct 值,或者 group by 之后的值 MIN()/MAX() 值,最简单的方式是扫描整个数据页,然后分组排序后,取 DISTINCT/MIN/MAX 值,但由于索引本身就有序并且完成了 group by 工作,如果可以直接借助于这个索引的有序性,那么扫描整个索引就可以避免二次排序的开销。这个功能支持带有聚合函数+GROUP BY或者聚合函数+DISTINCT或者DISTINCT的SQL使用。

这个功能类似于 INDEX_SKIP_SCAN,做一次对相同的 key 值进行 skip 动作,即可以跳过了索引上相同的段, 这样相比较与索引扫描而言,减少了很多的索引扫描,索引稀疏性越好,性能就会相对更好。

下面用一个简单的例子来说明GROUP_INDEX_SKIP_SCAN怎么应用在优化器。

CREATE TABLE t1(c1 INT, c2 INT, c3 INT, c4 INT);
CREATE UNIQUE INDEX i1_t1 ON t1(c1,c2,c3);
INSERT INTO t1 VALUES (1,1,1,1), (1,1,2,2), (1,3,3,3), (1,4,4,4), (1,5,5,5),
                      (2,1,1,1), (2,2,2,2), (2,3,3,3), (2,4,4,4), (2,5,5,5);
INSERT INTO t1 SELECT c1, c2, c3+5, c4+10  FROM t1;
INSERT INTO t1 SELECT c1, c2, c3+10, c4+20 FROM t1;
INSERT INTO t1 SELECT c1, c2, c3+20, c4+40 FROM t1;
INSERT INTO t1 SELECT c1, c2, c3+40, c4+80 FROM t1;
ANALYZE TABLE t1;

-- 下面的结果用到了group index skip scan,扫描方式是RANGE SCAN。
greatsql> explain SELECT c1, MIN(c2) FROM t1 GROUP BY c1;
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | t1    | NULL       | range | i1_t1         | i1_t1 | 10      | NULL |    3 |   100.00 | Using index for group-by |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+

/* 运行过程:
1、先找到第一列的唯一值,得到结果为1和2
SELECT distinct c1 FROM t1; 
2、根据第一列的分组结果在分组内对c2执行范围扫描
SELECT c1, MIN(c2) FROM t1 WHERE c1 = 1;
SELECT c1, MIN(c2) FROM t1 WHERE c1 = 2;*/

-- 如果没有联合索引,那么这条sql内部要创建临时表用于数据的临时处理,扫描方式是全表扫描。可见用GROUP_INDEX_SKIP_SCAN方式执行groupby可以节省空间和开销,提升执行效率。

greatsql> DROP INDEX i1_t1 ON t1;
greatsql> explain SELECT c1, MIN(c2) FROM t1 GROUP BY c1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra           |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  160 |   100.00 | Using temporary |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
二、get_best_group_min_max代码解释

group index skip scan是根据条件和聚合函数列以及distinct列,以及查找对应的联合索引进行判定能不能用group index skip scan,其中联合索引中聚合函数列或者distinct列之前的列为prefix列,即用来作为分组的依据的列,如果有多个除了第一列之外的范围列,那么选取遇到的第一个聚合函数列之前的列为前缀进行分组。

group index skip scan功能没有相关可以开启关闭的关键词,默认满足条件就可以使用,因此如果不想使用就要改变条件或者group by分组,只要不满足条件就不会使用联合索引。

int test_quick_select() {
  // 先判断GROUP_INDEX_SKIP_SCAN是否满足条件,不满足的话执行索引合并。
  AccessPath *group_path = get_best_group_min_max();
  // 不满足GROUP_INDEX_SKIP_SCAN执行索引合并,相关知识看之前知识介绍
}

AccessPath *get_best_group_min_max() {
  // 遍历表所有索引,找到联合索引
  for (uint cur_param_idx = 0; cur_param_idx < param->keys; ++cur_param_idx) {
    // 处理group by语句
    if (!join->group_list.empty()) {
      // 找到联合索引内的列字段相关mm tree
      if (tree) cur_tree = get_index_range_tree(cur_index, tree, param);
      // 该条件是否是等号条件或者IS NULL条件
          is_eq_range_pred = !(range->min_flag & is_open_range) &&
                             !(range->max_flag & is_open_range) &&
                             ((range->maybe_null() && range->min_value[0] &&
                               range->max_value[0]) ||
                              memcmp(range->min_value, range->max_value,
                                     cur_part->store_length) == 0);
    }
    // 处理distinct语句
    if ((join->group_list.empty() && join->select_distinct) ||
        is_agg_distinct) {
      检查distinct后面的列字段,必须包含联合索引前缀列或者所有列
    }
    // 获取第一个不在GROUP BY的索引字段
    first_non_group_part =
        (cur_group_key_parts < actual_key_parts(cur_index_info))
            ? cur_index_info->key_part + cur_group_key_parts
            : nullptr;
    // 获取聚合函数的索引字段
    first_non_infix_part = cur_min_max_arg_part
                               ? (cur_min_max_arg_part < last_part)
                                     ? cur_min_max_arg_part
                                     : nullptr
                               : nullptr;
    // 接下来是对sql语句的各种检查,确认能否用group index skip scan,代码略,具体见表一

    // 获取聚合函数列与第一个非GROUP BY列之间的差值
    key_infix_parts = cur_key_infix_len
                          ? (uint)(first_non_infix_part - first_non_group_part)
                          : 0;
    // 获取GROUP BY列与聚合函数列之间的列个数
    cur_used_key_parts = cur_group_key_parts + key_infix_parts;
    // 计算range范围内行数
    if (tree) {
      cur_quick_prefix_records = check_quick_select();
    }
    // 计算rows和cost
    cost_group_min_max();
  }
  // 最后生成AccessPath
  AccessPath *path = new (param->return_mem_root) AccessPath;
  path->type = AccessPath::GROUP_INDEX_SKIP_SCAN;
}

// 计算rows和cost,这里主要展示的是rows的计算公式
static void cost_group_min_max() {
  table_records = table->file->stats.records;
  keys_per_block = (table->file->stats.block_size / 2 /
                        (index_info->key_length + table->file->ref_length) +
                    1);
  num_blocks = (uint)(table_records / keys_per_block) + 1;
  if (index_info->has_records_per_key(group_key_parts - 1))
    // Use index statistics
    keys_per_group = index_info->records_per_key(group_key_parts - 1);
  else
    /* If there is no statistics try to guess */
    keys_per_group = guess_rec_per_key(table, index_info, group_key_parts);

  if (single_group)
    num_groups = 1;
  else {
    num_groups = (uint)(table_records / keys_per_group) + 1;
    if (range_tree && (quick_prefix_records != HA_POS_ERROR)) {
      quick_prefix_selectivity =
          (double)quick_prefix_records / (double)table_records;
      num_groups = (uint)rint(num_groups * quick_prefix_selectivity);
      num_groups = std::max(num_groups, 1U);
    }
  }
}

最后具体实现过程见代码GroupIndexSkipScanIterator::Read(),这里不再展开赘述。

表一:不能使用GROUP_INDEX_SKIP_SCAN的场合

场合cause说明
sql语句涉及多张表not_single_table只能支持单张表
sql语句带有rolluprollup
表没有可用联合索引not_covering
sql语句带有ORDER BY DESCcannot_do_reverse_ordering
sql语句不包含group_by或者DISTINCTnot_group_by_or_distinct
sql语句不包含COUNT,SUM,AVG,MIN,MAX聚合函数not_applicable_aggregate_function
查询条件生成了tree->mergesdisjuntive_predicate_present比如where c1=1 or c2>1,c1和c2属于不同索引
查询条件既包含MIN/MAX列又包含非MIN/MAX列minmax_keypart_in_disjunctive_query见下面例子
sql语句既带有distinct又带有min或者maxhave_both_agg_distinct_and_min_max
sql语句既带有distinct且distinct后面参数包含列和非列
group_by后面跟的不是列而是表达式group_field_is_expression比如GROUP BY c1+1
group by字段是联合索引的第一列group_attribute_not_prefix_in_index见下面例子
distinct后面字段是派生表的列derived_table
distinct后面字段是联合索引非前缀列或者非所有列select_attribute_not_prefix_in_indexdistinct后面字段必须是联合索引前缀列或者所有列
group by前面的聚合函数的列不是连续的联合索引列no_nongroup_keypart_predicate见下面例子
聚合函数(distinct column)字段是主键索引primary_key_is_clustered
聚合函数列小于等于group by字段aggregate_column_not_suffix_in_idx见下面例子
带有聚合函数的group by语句的条件不是聚合函数列之前一个列的等号条件non_equality_gap_attribute比如where c1>2 and c2>1 或者 where c1=2 and c2>1 见下面例子。而where c1>2 and c2=1是允许的
带有聚合函数的group by语句且没有条件,聚合函数列大于group by的列no_nongroup_keypart_predicate见下面例子
没有聚合函数但是有group by语句的条件带有非select和group by的条件列keypart_reference_from_where_clause比如where c1=1 or c3=1,见下面例子
带有聚合函数的group by语句的sql涉及聚合函数之后的列keypart_after_infix_in_query见下面例子
三、实际例子说明

接下来看几个例子来说明上面的代码。

greatsql> explain SELECT c1,c2,MIN(c3) FROM t1 where c1>1 and c2=1  GROUP BY c1;
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                                 |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+---------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | range | i1_t1         | i1_t1 | 15      | NULL |    2 |   100.00 | Using where; Using index for group-by |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+---------------------------------------+
                 "group_index_range": {
                    "potential_group_range_indexes": [
                      {
                        "index": "i1_t1",
                        "covering": true,
                        "index_dives_for_eq_ranges": true,
                        "ranges": [
                          "1 < c1"
                        ],
                        "rows": 2, 用group index skip scan扫描满足条件c1>1 and c2=1的一共2条数据,计算见下面
                        "cost": 0.55
                      }
                    ]
                  },
                  "best_group_range_summary": {
                    "type": "index_group",
                    "index": "i1_t1",
                    "group_attribute": "c3",
                    "min_aggregate": true,
                    "max_aggregate": false,
                    "distinct_aggregate": false,
                    "rows": 2,
                    "cost": 0.55,
                    "key_parts_used_for_access": [
                      "c1",
                      "c2",
                      "c3"
                    ],
                    "ranges": [
                      "1 < c1"
                    ],
                    "chosen": true
                  },
                  "skip_scan_range": {
                    "chosen": false,
                    "cause": "has_group_by"
                  },
                  "analyzing_range_alternatives": {
                    "range_scan_alternatives": [
                      {
                        "index": "i1_t1",
                        "ranges": [
                          "1 < c1"
                        ],
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,
                        "using_mrr": false,
                        "index_only": true,
                        "in_memory": 1,
                        "rows": 80, 单独用索引i1_t1满足1 < c1条件的有80条
                        "cost": 8.31051,
                        "chosen": false,
                        "cause": "cost"
                      }
                    ],
                    "analyzing_roworder_intersect": {
                      "usable": false,
                      "cause": "too_few_roworder_scans"
                    }
                  },
                  "chosen_range_access_summary": {
                    "range_access_plan": {
                      "type": "index_group",
                      "index": "i1_t1",
                      "group_attribute": "c3",
                      "min_aggregate": true,
                      "max_aggregate": false,
                      "distinct_aggregate": false,
                      "rows": 2,
                      "cost": 0.55,
                      "key_parts_used_for_access": [
                        "c1",
                        "c2",
                        "c3"
                      ],
                      "ranges": [
                        "1 < c1"
                      ]
                    },
                    "rows_for_plan": 2,
                    "cost_for_plan": 0.55,
                    "chosen": true
                  }
                }

/* 计算方式:
table_records = 160
keys_per_block = 391
num_blocks = (table_records / keys_per_block) + 1 = 160 / 391 + 1 = 1
keys_per_group = 80 -- group by字段分组后每组的行数
num_groups = (table_records / keys_per_group) + 1 = 160 / 80 + 1 = 3
quick_prefix_selectivity = quick_prefix_records / table_records = 80 / 160 = 0.5 -- 计算前缀选择系数
num_groups = num_groups * quick_prefix_selectivity = 3 * 0.5 = 2 */

下面看一下 distinct 的例子:

greatsql> explain SELECT distinct c1,c2 from t1;
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | t1    | NULL       | range | i1_t1         | i1_t1 | 10      | NULL |   10 |   100.00 | Using index for group-by |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
                 "group_index_range": {
                    "distinct_query": true, -- 这里说明用到了distinct关键字
                    "potential_group_range_indexes": [
                      {
                        "index": "i1_t1",
                        "covering": true,
                        "rows": 10,
                        "cost": 1.75
                      }
                    ]
                  },
                  "best_group_range_summary": {
                    "type": "index_group",
                    "index": "i1_t1",
                    "group_attribute": null,
                    "min_aggregate": false,
                    "max_aggregate": false,
                    "distinct_aggregate": false,
                    "rows": 10,
                    "cost": 1.75,
                    "key_parts_used_for_access": [
                      "c1",
                      "c2"
                    ],
                    "ranges": [
                    ],
                    "chosen": true
                  },

-- 当然也支持聚合函数+distinct+group by的组合。
greatsql> explain SELECT c1,sum(distinct c2) from t1 group by c1;
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | t1    | NULL       | range | i1_t1         | i1_t1 | 10      | NULL |   10 |   100.00 | Using index for group-by |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+

下面例子都是对上面表格的例子:

-- 查询条件既包含MIN/MAX列又包含非MIN/MAX列
greatsql> explain SELECT c1, MIN(c2) FROM t1 where c3>1 or c2<6 GROUP BY c1;
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | t1    | NULL       | index | i1_t1         | i1_t1 | 15      | NULL |  160 |    55.55 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
                  "best_covering_index_scan": {
                    "index": "i1_t1",
                    "cost": 17.4066,
                    "chosen": true
                  },
                  "setup_range_conditions": [
                  ],
                  "range_scan_possible": false,
                  "cause": "condition_always_true",
                  "group_index_range": {
                    "chosen": false,
                    "cause": "minmax_keypart_in_disjunctive_query"
                  },
                  "skip_scan_range": {
                    "chosen": false,
                    "cause": "has_group_by"
                  }

-- group by字段是联合索引的第一列
greatsql> explain SELECT c2, MIN(c1) FROM t1 GROUP BY c2 ;
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                        |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+------------------------------+
|  1 | SIMPLE      | t1    | NULL       | index | i1_t1         | i1_t1 | 15      | NULL |  160 |   100.00 | Using index; Using temporary |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+------------------------------+
                  "group_index_range": {
                    "potential_group_range_indexes": [
                      {
                        "index": "i1_t1",
                        "covering": true,
                        "usable": false,
                        "cause": "group_attribute_not_prefix_in_index"
                      }
                    ]
                  }

-- 带有聚合函数的group by语句且没有条件,聚合函数列大于group by的列
greatsql> explain SELECT c1, MIN(c3) FROM t1 GROUP BY c1;
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | index | i1_t1         | i1_t1 | 15      | NULL |  160 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-------------+
                  "group_index_range": {
                    "potential_group_range_indexes": [
                      {
                        "index": "i1_t1",
                        "covering": true,
                        "usable": false,
                        "cause": "no_nongroup_keypart_predicate"
                      }
                    ]
                  },

-- 带有聚合函数的group by语句的sql涉及聚合函数之后的列
greatsql> explain SELECT c1,MIN(c2) FROM t1 where c3=2 GROUP BY c1;
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | t1    | NULL       | index | i1_t1         | i1_t1 | 15      | NULL |  160 |    10.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
                  "group_index_range": {
                    "potential_group_range_indexes": [
                      {
                        "index": "i1_t1",
                        "covering": true,
                        "usable": false,
                        "cause": "keypart_after_infix_in_query"
                      }
                    ]
                  },

-- 带有聚合函数的group by语句的条件不是聚合函数列之前一个列的等号条件
greatsql> explain SELECT c1,c2,MIN(c3) FROM t1 where c3=2 GROUP BY c1;
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | t1    | NULL       | index | i1_t1         | i1_t1 | 15      | NULL |  160 |    10.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
                  "group_index_range": {
                    "potential_group_range_indexes": [
                      {
                        "index": "i1_t1",
                        "covering": true,
                        "usable": false,
                        "cause": "non_equality_gap_attribute"
                      }
                    ]
                  },
                  
-- 聚合函数列小于等于group by字段
greatsql> explain SELECT c1,c2,MIN(c2) FROM t1 where c1=2 and c2=2  GROUP BY c1,c2;
+----+-------------+-------+------------+------+---------------+-------+---------+-------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref         | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+-------+---------+-------------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ref  | i1_t1         | i1_t1 | 10      | const,const |   16 |   100.00 | Using index |
+----+-------------+-------+------------+------+---------------+-------+---------+-------------+------+----------+-------------+
                  "group_index_range": {
                    "potential_group_range_indexes": [
                      {
                        "index": "i1_t1",
                        "covering": true,
                        "usable": false,
                        "cause": "aggregate_column_not_suffix_in_idx"
                      }
                    ]
                  },

-- 带有聚合函数的group by语句且没有条件,聚合函数列大于group by的列
greatsql> explain SELECT c1,c2,MIN(c3) FROM t1  GROUP BY c1;
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | index | i1_t1         | i1_t1 | 15      | NULL |  160 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-------------+
                  "group_index_range": {
                    "potential_group_range_indexes": [
                      {
                        "index": "i1_t1",
                        "covering": true,
                        "usable": false,
                        "cause": "no_nongroup_keypart_predicate"
                      }
                    ]
                  },

-- 没有聚合函数但是有group by语句的条件带有非select和group by的条件列
greatsql> explain SELECT c1,c2 FROM t1 where c1=1 or c3=1 GROUP BY c1,c2;
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | t1    | NULL       | index | i1_t1         | i1_t1 | 15      | NULL |  160 |    55.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
                  "group_index_range": {
                    "potential_group_range_indexes": [
                      {
                        "index": "i1_t1",
                        "covering": true,
                        "usable": false,
                        "cause": "keypart_reference_from_where_clause"
                      }
                    ]
                  },
四、总结

从上面group index skip iscan的代码我们了解了组别跳跃扫描的使用条件和使用场景,以及使用规则,这个功能让一些涉及分组和聚合函数执行的时候直接读联合索引就可以很快得到分组数据,避免了无效列的读取,提高了效率,但sql使用限制比较多,因此创建联合索引和使用的时候要注意sql语句与联合索引的匹配才能用到这个功能。

麒麟V10系统命令实战指南:从入门到精通 本文为麒麟V10操作系统用户提供了一份从入门到精通的命令行实战指南。内容涵盖文件管理、进程监控、网络诊断、文本处理及系统管理等核心场景,旨在帮助用户转变思维,掌握高效解决实际问题的系统命令技巧,从而提升在麒麟V10环境下的运维与工作效率。 阅读详情

相关推荐

Flink CDC 3.0 详解

Flink CDC 是基于数据库日志 CDC(Change Data Capture)技术的实时数据集成框架,支持全增量一体化、无锁读取、并行读取、表结构变更自动同步、分布式架构等高级特性。配合Flink 优秀的管道能力和丰富的上下游生态,Flink CDC 可以高效实现海量数据的实时集成。Flink CDC 于2023年12月7日重磅推出其全新的 3.0 版本,3.0版本的发布对 Flink CDC 而言具有里程碑的意义,自此 Flink CDC 从捕获数据变更的Flink 数据源正式迈向为以 Flink

奔跑的蜗牛的博客 2639

Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)

Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)

要努力去发光,而不是被照亮~ 130

FastAPI + Layui:打造企业级敏捷开发后台管理系统

本文详细介绍了如何使用FastAPI和Layui构建高效的企业级后台管理系统。FastAPI提供高性能的异步API支持,而Layui则简化了前端开发流程,两者结合可实现快速开发和部署。文章涵盖了从环境搭建、核心组件开发到性能优化的全流程,特别适合需要敏捷开发的企业项目。

weixin_29045585的博客 555

Spring Boot 数据源与 MyBatis(-Plus) 自定义配置详解

本文详解 Spring Boot 中单数据源环境下 HikariCP 与 MyBatis-Plus 的自定义配置。通过手动声明 SqlSessionFactory,实现下划线转驼峰、日志输出、一级缓存策略等精细化控制。结合 HikariCP 核心参数调优(如最大连接数、连接生命周期),确保高性能与稳定性。适用于仅需访问单一数据库的场景,避免多数据源复杂性,兼顾性能与可维护性。

BADAO_LIUMANG_QIZHI的博客 198

Silvaco Atlas(五)——物理模型总结

物理模型总结

qsz1425AWD2121的博客 2151

Qwen-Image-2.1本地图像生成整合包:8G显存一键部署指南

文生图模型是AI视觉创作的核心技术,其本地化部署需兼顾模型能力、硬件适配与工程易用性。Qwen-Image-2.1作为支持文本生成、图像编辑与多图参考的统一架构模型,突破了传统Diffusion模型在消费级显卡上的运行瓶颈。通过AWQ量化、vLLM推理优化与ComfyUI节点重构,实现8G显存稳定运行,显著降低CUDA环境配置复杂度。该方案面向设计师、教师与普通用户,提供开箱即用的本地AI图像工作流,适用于电商主图迭代、教学配图生成与IP角色延展等真实场景,让高质量图像生成真正走出云端,落地桌面。

weixin_29082143的博客 398

Docker Compose:搭建本地多服务开发环境

从一个 Java API、PostgreSQL 和 Redis 开始,配置服务名、端口、环境变量、健康检查、数据卷和依赖关系。理解 Compose 文件如何被解析、项目如何隔离,以及哪些数据不能依赖“重建容器”保留,能够把个人开发环境整理成团队可复用的启动入口。

m0_62221377的博客 458

ios生成海报使用dom-to-image中toPng报错isTrusted:true

主要是ios中safari浏览器解析错误,引起的报错,主要引起原因toPng会调用toSvg,toSvg直接将html标签内容通过svg中foreignObject标签直接包裹,然后转base64, 通过data:image/svg+xml;解决办法:二次生成(调用两次),判断ios or 安卓,生成第一次生成,不用参数,例如:scale: 3,在第二次生成再进行清晰度操作,避免多次生成,引起性能消耗。以上种种很多案例,使用canvase进行转换的,标签丢失,直接toPng版本的,兼容性问题。

weixin_44601570的博客 2105

AI全栈实战 | 3.9-01 爬虫入门:requests 加 BeautifulSoup 三分钟跑通,但 90% 的人栽在这 4 个“礼貌“问题上

⚠️:本文内容仅用于。请遵守目标站点的服务条款与相关法律法规,不得对目标服务造成负担,不得采集、传播个人隐私数据。技术是中性的,边界在你手里。爬虫的入门技术(requests 发请求 + BeautifulSoup 解析)三分钟就能学会。真正区分"会用爬虫"和"会做爬虫的人"的,是这篇我们跑通"请求→解析→存储"的最小闭环,然后重点把"合规与礼貌"这根弦给你绷上。

源码获取 | 运行部署 | 定制二开修改,如果回复不及时请耐心等待,请见谅。 411

ABP框架实战:从零搭建多租户应用(含完整代码示例)

本文详细介绍了如何使用ABP框架从零构建多租户SaaS应用,涵盖多租户架构设计、数据隔离实现、租户管理及性能优化等核心内容。通过完整的代码示例,帮助开发者快速掌握企业级数据隔离系统的开发技巧,提升SaaS应用开发效率。

weixin_29247219的博客 263

NX二次开发 计算多个体的最小包围盒

多个体的最小包围盒

weixin_72680115的博客 430

数据库主键选型指南:BIGINT vs VARCHAR

主键选型关乎数据库性能与架构演进。BIGINT(如雪花算法)在顺序写入、二级索引开销和分布式生成上优势显著,适合高并发核心链路;而传统 VARCHAR UUIDv4 因随机性导致频繁页分裂、存储膨胀,性能堪忧。UUIDv7 通过时间戳有序性实现高效追加写入,结合 BINARY(16) 存储可兼顾性能与安全,成为现代微服务新选择。关键在于:主键应稳定、无业务含义,优先选用趋势递增的紧凑类型,并根据场景合理采用双主键模式或 ULID 等替代方案。

小李同学的博客 301

maxim max96712+max96717调试笔记

mxxim max96712 和max96717调试笔记

libuping2023的博客 1万+

自动化运维选型:四条技术路线的能力边界,以及它们为什么不能互相替代

自动化运维怎么选型?拆解配置管理、基础设施即代码、作业调度与场景化编排四条技术路线边界,分析账本归属与时间语义差异,给出四种架构组合与判断点,帮企业看清为何不能互相替代。

trainsignalcn的专栏 327

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

C++算法(七)

C++ stack算法题和一些客观题

2503_94479566的博客 370

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

不用再为文本搜索单独维护一套系统!全文检索深度解析

为了给商品、客服工单或者企业知识库增加一个搜索框,企业往往需要在业务数据库之外,再部署一套Elasticsearch。数据库保存商品、订单和用户等真实业务数据,搜索引擎保存另一份文本索引;两套系统之间,还需要通过CDC、消息队列或者同步程序持续传输数据。一个看似简单的搜索需求,最后可能变成两套集群、两份数据和一条必须保持稳定的数据链路。由此产生的问题并不少:数据库已经完成更新,搜索结果却可能尚未同步;业务查询同时涉及文本、库存、权限和订单状态时,应用需要在两个系统之间来回组合结果;

TiDB 社区干货传送门 250

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

IDEA 简介:从零认识 IntelliJ IDEA

IDEA 全称,是由 JetBrains 公司开发的一款主流 Java 集成开发环境(IDE)。它集代码编辑、编译、调试、测试、版本控制、构建管理等功能于一体,被大量 Java 开发者视为日常开发的首选工具。所谓 IDE(Integrated Development Environment,集成开发环境),就是把写代码所需的各类工具整合到同一个界面中。相比只用文本编辑器加命令行编译,IDE 能显著减少重复操作,让开发者把精力集中在业务逻辑上。

ztI10的博客 152

48-逻辑结构表空间段区块

默认 10%,意思是块里的空闲空间低于 10% 时就不再插入新行,留着给已有行的 UPDATE 变胖用。注意 INSERT 过程中每次申请新区,都是一次潜在的空间报错点,如果表空间满了,此刻就会抛 ORA-01653。剩下的空间被谁占了?段的每个区可以落在表空间的任一数据文件,所以 2T 的表在 32G 单文件上限(smallfile)的世界里照样能活——多加文件就行。,一张只有两个 CLOB 字段的表,实际在库里是三个段(表段 + 两个 LOBSEGMENT),空间统计漏算 LOB 段是新手常犯的错。

yuhou25的博客 286

市场调查与统计分析专业大学期间考证怎么安排?

市场调查与统计分析专业的考证规划,适合从一份完整的调查报告倒推:能不能设计问卷、判断样本是否合理、清洗数据,再把分析结果转化为业务建议?围绕这条能力链安排证书,比单纯按“热门程度”报名更有针对性。结合2026年的学习安排,并为2027年的实习、升学和求职预留时间,大学期间可以重点考虑六类证书:全国计算机等级考试证书、大学英语四级或六级成绩报告单、CDA数据分析师、初级统计专业技术资格证书、微软办公软件国际认证中的Excel相关证书,以及软考数据库系统工程师资格证书。

cc572502653的博客 158

Oracle 19c 在线重定义后旧表空间清理及 ASM 存储资源回收 SOP

Oracle 19c 在线重定义后旧表空间清理与 ASM 存储资源回收 SOP

「YYL DBA手记」 数据库 · 架构 · 运维 · 实战 yyldba.blog.youkuaiyun.com 486

大表 ALTER TABLE 怎么做?NineData OnlineDDL 配置与执行实践

介绍NineData OnlineDDL大表变更实践,涵盖执行策略、切表控制、拷贝批次与可视化进度追踪。

dbasenewbie的博客 270

Spring事务和事务传播机制

3. 可重复读(REPEATABLE READ): 事务不会读到其他事务对已有数据的修改, 即使其他事务已提交. 也就可以确保同⼀事务多次查询的结果⼀致, 但是其他事务新插⼊的数据, 是可以感知到的. 这也就引发了幻读问题. 可重复读, 是 MySQL 的默认事务隔离级别.p1事务可以认为是⽗事务, 嵌套事务是⼦事务. ⽗事务出现异常, ⼦事务也会回滚, ⼦事务出现异常, 如果不进⾏处理, 也会导致⽗事务回滚.

txxzjmzlh的博客 205

从事件风暴到因果收敛:Mopheus多智能体协同事件触发架构与防循环实践

在构建生产级多智能体协同引擎的过程中,我们总结了三条核心工程准则:1.不可变因果链优于事后关系反查在高度并发且异步的多智能体网络中,因果关系的追溯绝不能依赖事后查库反查关联;将只增的因果祖先链显式封装在事件载荷与任务上下文中,以O(1)的内存检查实现确定性极高的闭环防范。2.生产端纯粹广播与消费端前置极速过滤是高吞吐的基石遵循严格的Pub/Sub模式,让核心业务实体与下游智能体编排解耦;通过调度层内存条件字典求值(conditions),在纳秒级内过滤掉99%的无关事件,保障零业务侵入与极高的系统吞吐。

weixin_54551388的博客 240

Redis 入门详解:核心特性、重要文件与全局命令实战

本文系统介绍了Redis的核心特性、重要文件及全局命令。Redis凭借内存存储、C语言实现和单线程设计,具备极高的读写性能;支持持久化(RDB/AOF)、主从复制与集群架构,适用于缓存、排行榜、计数器等场景,但不适合存储海量冷数据。文中还解析了常见面试题,如为何快速、单线程原理、持久化机制、KEYS *禁用原因及过期键处理策略,强调其在高并发场景下的优势与局限。

2303_77004089的博客 198

【FDE前沿部署工程师实战教程】第62章:Enterprise AI Developer Platform——企业级 AI 开发者平台与低代码 AI 应用工厂

企业从AI能力平台迈向AI应用生产平台,需构建统一的企业级AI开发者平台,将模型、RAG、Agent、Workflow等能力封装为可视化、可复用的开发工具。通过PromptIDE、AgentBuilder、WorkflowBuilder等模块,实现低代码与专业代码协同,降低开发门槛。平台覆盖应用全生命周期管理,集成测试评估、安全治理与CI/CD发布体系,推动AI应用规模化、标准化交付。最终形成“需求→设计→开发→测试→发布→运营”的企业AI应用工厂模式,助力制造等行业的智能化转型。

IT古董 305

SpringBoot 美容院管理系统:232个Java文件315个接口,5套角色从预约到发货一整条链路

# SpringBoot 美容院管理系统:232个Java文件315个接口,5套角色从预约到发货一整条链路 > 这是一套基于 Spring Boot 的美容院管理系统源码,配套 `db.sql` 建表脚本,业务覆盖美容项目预约、美容用品销售、购物车、订单流转、收货地址、库存销量统计,以及技师/前台/会员三类内部角色的业绩统计。形态是**前后台双端**:前台是顾客和会员用的商城站,后台是管理员、技

面朝大海 236
上一篇: GreatSQL 为何选择全表扫描而不选索引
下一篇: dbops 助力 GreatSQL 单机架构安装部署
GreatSQL社区
博客等级 码龄5年 1210粉丝 390原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值