首页 > 其他分享 >TiDB 优化器逻辑优化之 OR 表达式条件消除

TiDB 优化器逻辑优化之 OR 表达式条件消除

时间:2023-12-18 14:01:06浏览次数:28  
标签:11 arg t1 jan TiDB NULL 优化 id 表达式

作者: jansu-dev



第一章 背景介绍

**好久没发文章了, 发篇曾经研究过的一篇水文.**

   通常来说, 永假的 OR 谓词条件是可以被消除的, 并且在通用数据库上可以见到对应逻辑.

   如果不消除, 会引入额外的筛选机制, 导致大量计算资源被消耗, 引发 SQL 性能降低的现象.



第二章 理论概括

以下述代码块为例, 演示具体恒假析取表达式怎么被消除:
create table jan(id int, name int);
explain select * from jan where id=123 or 1=2;
select version();
  1. PostgreSQL 实验
  1. 可以看到 Filter: (id = 123) 已经直接将 or 1=2 的永假析取条件消除了.
test=# explain select * from jan where id=123 or 1=2;
                     QUERY PLAN                      
-----------------------------------------------------
 Seq Scan on jan  (cost=0.00..21.25 rows=4 width=62)
   Filter: (id = 123)
(2 rows)


test=# select version();
                                                      version                                                      
-------------------------------------------------------------------------------------------------------------------
 PostgreSQL 13.12 on arm-apple-darwin22.4.0, compiled by Apple clang version 14.0.3 (clang-1403.0.22.14.1), 64-bit
(1 row)
  1. Oceanbase 实验
  1. 可以看到 1 = 2 result is FALSE 并在 Output & filters 中没有出现相关筛选, 说明也消除了永假析取条件.

TiDB 优化器逻辑优化之 OR 表达式条件消除_mysql

并且 [OB 官网](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000035699#73f4b831-eadb-45fa-8b3c-79cd86d02e34) 有对该种情况进行描述.

TiDB 优化器逻辑优化之 OR 表达式条件消除_PostgreSQL_02



第三章 现状分析

上面介绍了 OB 和 PG 该功能的实现, 下面演示当前 TiDB 的实现情况.

   截止至 v7.1.1 TiDB 在单表查询时, or(eq(test.jan.id, 123), 0) 的恒假析取条件直接下推至 TiKV, 而不是直接在执行计划中消除.
mysql> explain select * from jan where id=123 or 1=2;
+-------------------------+----------+-----------+---------------+--------------------------------+
| id                      | estRows  | task      | access object | operator info                  |
+-------------------------+----------+-----------+---------------+--------------------------------+
| TableReader_7           | 10.00    | root      |               | data:Selection_6               |
| └─Selection_6           | 10.00    | cop[tikv] |               | or(eq(test.jan.id, 123), 0)    |
|   └─TableFullScan_5     | 10000.00 | cop[tikv] | table:jan     | keep order:false, stats:pseudo |
+-------------------------+----------+-----------+---------------+--------------------------------+
3 rows in set (0.00 sec)

mysql> select version();
+--------------------+
| version()          |
+--------------------+
| 5.7.25-TiDB-v7.1.1 |
+--------------------+
1 row in set (0.00 sec)



第四章 问题分析

其实, 单表查询的使用方法并不一定会造成问题, 但是如果在多表 join 的情况下, 便会加剧该情况. 如 [Issue-45785](https://github.com/pingcap/tidb/issues/45785) 中提到的, 在 TiDB instance 侧仍需要 CARTESIAN inner join 算子过滤 or(and(eq(edmp.t1.a, edmp.t2.a), eq(edmp.t1.b, 1)), 0). 比较理想的情况, 应该是从 CARTESIAN inner join 变为 inner join.
CREATE TABLE `t1` (
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  `c` int(11) DEFAULT NULL,
  KEY `idx_a` (`a`)
);
CREATE TABLE `t2` (
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  `c` int(11) DEFAULT NULL,
  KEY `idx_a` (`a`)
);
mysql> explain select /*+ INL_JOIN(t2)*/ * from t1,t2 where t1.a=t2.a and t1.b = 1 or 1=2;
+------------------------------+-----------+-----------+---------------+-----------------------------------------------------------------------------------------+
| id                           | estRows   | task      | access object | operator info                                                                           |
+------------------------------+-----------+-----------+---------------+-----------------------------------------------------------------------------------------+
| HashJoin_9                   | 100000.00 | root      |               | CARTESIAN inner join, other cond:or(and(eq(edmp.t1.a, edmp.t2.a), eq(edmp.t1.b, 1)), 0) |
| ├─TableReader_12(Build)      | 10.00     | root      |               | data:Selection_11                                                                       |
| │ └─Selection_11             | 10.00     | cop[tikv] |               | or(eq(edmp.t1.b, 1), 0)                                                                 |
| │   └─TableFullScan_10       | 10000.00  | cop[tikv] | table:t1      | keep order:false, stats:pseudo                                                          |
| └─TableReader_14(Probe)      | 10000.00  | root      |               | data:TableFullScan_13                                                                   |
|   └─TableFullScan_13         | 10000.00  | cop[tikv] | table:t2      | keep order:false, stats:pseudo                                                          |
+------------------------------+-----------+-----------+---------------+-----------------------------------------------------------------------------------------+
6 rows in set, 1 warning (0.00 sec)



第五章 解决方案

从 PG 的实现方式来看, 会有个 canonicalize\_qual 函数试图将表达式强制转换为规范的 and-of-or 或 or-of-and形式. 在这个过程中, 会调用到 find\_duplicate\_ors 函数(如下所示部分), /\* Get rid of any constant inputs \*/ 注释部分会将永假析取条件直接消除.
/*
 * find_duplicate_ors
 *    Given a qualification tree with the NOTs pushed down, search for
 *    OR clauses to which the inverse OR distributive law might apply.
 *    Only the top-level AND/OR structure is searched.
 *
 * While at it, we remove any NULL constants within the top-level AND/OR
 * structure, eg in a WHERE clause, "x OR NULL::boolean" is reduced to "x".
 * In general that would change the result, so eval_const_expressions can't
 * do it; but at top level of WHERE, we don't need to distinguish between
 * FALSE and NULL results, so it's valid to treat NULL::boolean the same
 * as FALSE and then simplify AND/OR accordingly.  Conversely, in a top-level
 * CHECK constraint, we may treat a NULL the same as TRUE.
 *
 * Returns the modified qualification.  AND/OR flatness is preserved.
 */
static Expr *
find_duplicate_ors(Expr *qual, bool is_check)
{
    if (is_orclause(qual))
    {
        List       *orlist = NIL;
        ListCell   *temp;

        /* Recurse */
        foreach(temp, ((BoolExpr *) qual)->args)
        {
            Expr       *arg = (Expr *) lfirst(temp);

            arg = find_duplicate_ors(arg, is_check);

            /* Get rid of any constant inputs */
            if (arg && IsA(arg, Const))
            {
                Const      *carg = (Const *) arg;

                if (is_check)
                {
                    /* Within OR in CHECK, drop constant FALSE */
                    if (!carg->constisnull && !DatumGetBool(carg->constvalue))
                        continue;
                    /* Constant TRUE or NULL, so OR reduces to TRUE */
                    return (Expr *) makeBoolConst(true, false);
                }
                else
                {
                    /* Within OR in WHERE, drop constant FALSE or NULL */
                    if (carg->constisnull || !DatumGetBool(carg->constvalue))
                        continue;
                    /* Constant TRUE, so OR reduces to TRUE */
                    return arg;
                }
            }

            orlist = lappend(orlist, arg);
        }

        /* Flatten any ORs pulled up to just below here */
        orlist = pull_ors(orlist);

        /* Now we can look for duplicate ORs */
        return process_duplicate_ors(orlist);
    }
    else if (is_andclause(qual))
    {
        List       *andlist = NIL;
        ListCell   *temp;
......
因此, TiDB 其实可以仿制类似逻辑, 在 buildSelection 处理 where 条件时, 利用递归的方法实现这个功能.

   **期待大佬们解决这个简单的问题了, 望产品逐渐完善!**



第六章 文献参考

  1. PG 源代码
  2. PG 源代码解读-PostgreSQL 源码解读(33)- 查询语句#18(查询优化-表达式预处理#3
  3. TiDB 源代码

标签:11,arg,t1,jan,TiDB,NULL,优化,id,表达式
From: https://blog.51cto.com/tidb/8873047

相关文章

  • 通过 Sysbench 在低配置低数据基础上分别压测 MySQL 和 TiDB,实际结果 TiDB 出乎我的想
    作者:tidb菜鸟一只背景最近要上一个新项目,原来提供的是一个主从mysql数据库,两台16C64G的主机(还有个预发环境也是mysql主从,2个4C8G主机),感觉不是很靠谱,所以想要切换成tidb,所以对两边进行了压测(包括预发),两边磁盘都是垃圾机械盘,性能不说了,但是两边都垃圾,对比数据还是比较靠谱的。......
  • 如何利用烛龙和谷歌插件优化CLS(累积布局偏移) | 京东云技术团队
    简介CLS衡量的是页面的整个生命周期内发生的每次意外布局偏移的最大突发性_布局偏移分数_。布局变化的发生是因为浏览器倾向于异步加载页面元素。更重要的是,您的页面上可能存在一些初始尺寸未知的媒体元素。这种组合意味着浏览器在加载完成之前无法确定单个元素将占用多少空间。因......
  • 火山引擎AB测试:企业产品优化主题分享在北京举办
    更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群近日,火山引擎数智平台在北京举办了“超话数据:企业产品优化分享”的活动。该活动邀请了火山引擎的产品专家及解决方案专家,三位专家结合抖音的产品优化经验和外部企业应用视角,分享在产品优化方面的......
  • 火山引擎AB测试:企业产品优化主题分享在北京举办
    更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群近日,火山引擎数智平台在北京举办了“超话数据:企业产品优化分享”的活动。该活动邀请了火山引擎的产品专家及解决方案专家,三位专家结合抖音的产品优化经验和外部企业应用视角,分享在产品优化方......
  • 如何快速优化几千万数据量的订单表
    前言为了保证有一个更健康的身体,所以慢慢降低了更新频率,在有了更多休息时间的前提下,思考了一下接下来准备分享的一些内容。决定在更新一些技术干货的同时,会穿插一些架构知识,放在单独的专栏里面,希望大家能喜欢,里面包含了这些年工作中遇到的一些内容,以及自己充电后总结的一些知识,......
  • 持续性能优化,从容应对挑战——澜起科技全新第五代津逮®CPU上市!
    澜起科技于今日正式向外界发布其全新第五代津逮®CPU,旨在以多方面的性能优化应对AI、HPC、数据服务、网络/5G、存储等严苛工作负载的挑战。 澜起科技第五代津逮®CPU,基于英特尔®第五代至强®可扩展处理器内核(代号:EmeraldRapids),通过了澜起科技安全预检测(PrC)测试,是面向本土市场的x......
  • 神经网络优化篇:机器学习基础(Basic Recipe for Machine Learning)
    机器学习基础下图就是在训练神经网络用到的基本方法:(尝试这些方法,可能有用,可能没用)这是在训练神经网络时用到地基本方法,初始模型训练完成后,首先要知道算法的偏差高不高,如果偏差较高,试着评估训练集或训练数据的性能。如果偏差的确很高,甚至无法拟合训练集,那么要做的就是选择一个新......
  • 前端体验优化(3)——后端
    前端很多时候是不会接触到后端的工作,不过我们公司由于历史原因,维护了大量的Node.js服务。所以也积累了一些后端优化的经验,主要分两块Node.js和数据库。一、Node.jsNode.js的监控没有从0开始,业务逻辑的日志直接记录在阿里云中,性能监控部署的是阿里云提供的系......
  • k8s集群优化
    1、内核参数调化fs.file-max=1000000#max-file表示系统级别的能够打开的文件句柄的数量,一般如果遇到文件句柄达到上限时,会碰到#"Toomanyopenfiles"或者Socket/File:Can’topensomanyfiles等错误。#配置arpcache大小net.ipv4.neigh.default.gc_thresh1=1024#存......
  • 群智能优化算法测试函数matlab源代码
    globalM;creatematrix(2);%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%画ackley图。%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%ackleyxfrom[-55]%x=-5:0.01:5;%[x,y]=meshgrid(x);%temp1=x.^2+y.^2;%temp2=cos(2*pi*x)+cos(2*pi*y);%z=20+exp(1)-20*exp(-0.2*sqrt(te......