当前位置: 首页 > 科技观察

WhereFieldin(...)是如何工作的?

时间:2023-03-16 13:48:08 科技观察

我们每天写SQL的时候,子查询应该算是常客了。MySQL为子查询的执行准备了各种优化策略。接下来,我将写一系列文章,介绍子查询的各种优化策略是如何执行的。本文从一个包含最简单的in条件的查询开始,介绍in条件where字段in(8,18,88,...),其值都是常量,是如何执行的。虽然这不是子查询,但我们就把它当作子查询的邻居。用它作为子查询系列的开始,离离子查询又近了一步^_^。本文内容基于MySQL8.0.29源码。正文1.概述为了使SQL语句执行得更快,MySQL进行了各种优化。wherefieldin(8,18,88,...)是condition中的常量,看起来是最简单的形式,执行过程中好像也没有什么需要优化的地方,但是MySQL还是对它进行了优化。这个incondition有两种执行方式:二分查找和循环比较。MySQL会先使用二分查找的方式执行。如果不满足条件,则回退到循环比较法。2、二分查找判断in条件中括号内的值是否匹配记录字段的值。与循环比较法相比,二分查找将时间复杂度从O(N)降低到O(logN),大大减少了需要比较的次数,提高了SQL的执行效率。(1)构造二分查找数组二分查找很好,但是需要满足一定的条件才能使用:in条件括号内的所有值都是常量,也就是说不能包含任何表中的字段,也不能包含系统变量(如@@tmp_table_size)或自定义变量(如@a),简而言之,它不能包含任何可以改变的东西。in条件括号中所有值的数据类型必须相同。举个反例:wherefieldin(1,8,'10')是一个既包含整数又包含字符串的in条件。in条件括号中所有值的类型,以及字段本身的类型不能是json。满足以上三个条件,就有了使用二分查找的基础。接下来我们看一下判断以上三个条件是否满足的主要过程:第一步,循环遍历in条件中括号内的每一个值,看是否都是常量,只要a结束循环遇到不是常量的值。boolItem_func_in::resolve_type(THD*thd){......//判断in条件字段本身是否为json类型boolcompare_as_json=(args[0]->data_type()==MYSQL_TYPE_JSON);......boolvalues_are_const=true;项目**arg_end=args+arg_count;for(Item**arg=args+1;arg!=arg_end;arg++){//判断in条件括号内的值是否为json类型compare_as_json|=(arg[0]->data_type()==MYSQL_TYPE_JSON);//判断in条件括号内的值是否为常量if(!(*arg)->const_item()){//in条件括号内的值,只要一个不是常量,记录values_are_const=错误的;//@todo-重写为has_subquery()???如果((*arg)->real_item()->type()==Item::SUBSELECT_ITEM)dep_subq_in_list=true;//然后结束循环break;}}......}上面代码中还做了另外一件事,就是判断in条件括号内的所有值,以及in条件字段是否为json类型。args[0]表示in条件字段,args[1~N]是in条件括号中的值。第2步,计算in的条件括号内的所有值,一共有几种数据类型。boolItem_func_in::resolve_type(THD*thd){......uinttype_cnt=0;对于(uinti=0;i<=(uint)DECIMAL_RESULT;i++){if(found_types&(1U<中的所有表达式都是const3.否比较JSON(在这种情况下使用通用JSON比较器)*/boolbisection_possible=type_cnt==1&&//1values_are_const&&//2!compare_as_json;//3......}判断二分查找是否满足的三个前置条件,逻辑就是上面的,不要太复杂。对于whererow(filed1,field2)in((1,5),(8,10),...)行类型的in条件,MySQL也会尝试使用二分查找,本文不涉及这些逻辑.(2)对数组进行填充排序,需要用到二分查找的方法。仅满足3个先决条件是不够的。还要求in条件括号内的值必须排序。接下来,是时候继续前进了。即对in条件括号中的值进行排序。排序过程分为两步:第一步仍然是使用循环将in条件括号中的每个值依次添加到数组中。第2步,将所有的值加入数组后,对数组元素进行排序。//items是用来存放in条件括号中所有值的数组boolin_vector::fill(Item**items,uintitem_count){used_count=0;for(uinti=0;inull_value)used_count++;}断言(已用计数<=计数);//排序数组元素resize_and_sort();//True=至少找到一个空值。returnused_countnull_value)returnfalse;//对于非空值二分查找returnstd::binary_search(base.begin(),base.end(),result,Cmp_longlong());}3.循环比较前面提到,使用二分查找执行in条件判断是有前提条件的,如果条件不满足,那么只能回退到原来的执行方式。原来的执行方式是循环遍历条件括号中的值,与存储引擎读取的记录字段值一一比较。只要遇到相等的值,说明这条记录符合in条件,循环结束,需要将这条记录返回给客户端。如果一路循环in条件括号中的最后一个值,并没有找到存储引擎读取到的记录字段值,说明这条记录不符合in条件,这条记录不需要发送给客户端。longlongItem_func_in::val_int(){......for(uinti=1;ireal_item()->type()==NULL_ITEM){have_null=true;继续;}//获取in条件括号中的值,用什么类型比较,记录为cmp_typeItem_resultcmp_type=item_cmp_type(left_result_type,args[i]->result_type());in_item=cmp_items[(uint)cmp_type];断言(in_item);if(!(value_added_map&(1U<<(uint)cmp_type))){//把读取的记录中的in字段值转换为cmp_type类型in_item->store_value(args[0]);value_added_map|=1U<<(uint)cmp_type;如果(current_thd->is_error())返回error_int();}//比较读取记录记录的in字段值是否等于当前循环的值constintrc=in_item->cmp(args[i]);//rc为比较结果,false表示相等if(rc==false)return(longlong)(!negated);have_null|=(rc==UNKNOWN);如果(current_thd->is_error())返回error_int();}......}4.总结不包含子查询的in条件,存储引擎将记录的读取字段值录入行比较,有二分查找和循环比较两种方法。二分查找虽然有3个条件,但这些条件其实很容易满足。因此,在大多数情况下,可以使用二分查找来获得更高的性能。效率。本文转载自微信公众号“一树一溪”,可通过以下二维码关注。转载本文请联系艺书艺熙公众号。