热门标签 | HotTags
当前位置:  开发笔记 > 后端 > 正文

关于MariaDB数据库中的子查询优化

下面是一个图显示允许在SQL语言中所有类型的子查询和优化器来处理它们的策略不着颜色的地区代表不同类型的子查询,例如:SubqueriesthathaveformxIN(SELECT...)SubqueriesthatareintheFROMclause..andsoforth每个没有着色的区域的大小大致
下面是一个图显示允许在SQL语言中所有类型的子查询和优化器来处理它们的策略
不着颜色的地区代表不同类型的子查询,例如:
Subqueries that have form x IN (SELECT ...)
Subqueries that are in the FROM clause
.. and so forth

每个没有着色的区域的大小大致上对应于这种子查询(例如: x IN (SELECT ...))有多么重要(i.e:频繁使用. ) 和 EXISTS (SELECT ...) 相对而言不那么重要.
彩色的区域表示优化/执行策略应用于处理各种类型的子查询。.
优化的颜色显示MySQL / MariaDB哪个版本的是可用的(参见下面的说明
subquery-optimizations-map2

任何特性标记为“MariaDB 5.3”或“MariaDB只有5.3”也存在于 MariaDB 5.5.
关于各自优化的链接: Derived table optimizations
也可参照:http://blog.sina.com.cn/s/blog_aa8dc60801012pzc.html  semi-join subquery

半联接子查询有一种格式

SELECT ... FROM outer_tables WHERE expr IN (SELECT ... FROM inner_tables ...) AND ...

即,子查询是一个IN-subquery,并且它位于WHERE子句中。这最重要的部分    
是半联接子查询,我们只对外表的记录在子查询配置的感兴趣 

可以很“自然”执行它,通过从欧洲国家开始和检查是否有这么多人口城市:

mariadb5.3 <wbr>子查询优化图

这个半联接属性还允许“倒退”执行:我们可以开始从大城市,并检查哪些国家:

mariadb5.3 <wbr>子查询优化图

对比,让我们改变子查询被非半联接:


非半联接

select * from Country where Country.COntinent='Europe' and

  (Country.Code in (select City.country from City where City.Population>1*1000*1000)

    or Country.SurfaceArea > 100*1000  -- 添加这个部分

   )
mariadb5.3 <wbr>子查询优化图

 

 与内连接区别

半联接操作是类似于普通关系连接。有一个区别在于:对半连接,你对外表的行不在乎匹配内部表多少行。在上面的国家与大城市的例子,德国将会返回一次,即使它有三个城市人口超过一百万的。

semi-join subquery优化策略
1:Table pullout optimization

有以下两个要求
表撤出,只可能在半连接子查询。  

表撤销是基于唯一/主键定义。

eg:

select * from City where City.Country in (select Country.Code from Country where Country.Population < 100*1000);

1是半联接子查询,2是code 是唯一索引/主键

优化后:

select `*   from `world`.`City` join `world`.`Country` where ((`world`.`City`.`Country` = `world`.`Country`.`Code`) and (`world`.`Country`.
`Population` < (100 * 1000)))
2:DuplicateWeedout 策略
这个想法是像运行常规内连接一样运行半联接 ,然后使用临时表消除重复记录的组合。
eg:select * from Country where Country.code IN (select City.Country from City where City.Population > 0.33 * Country.Population and City.Population > 1*1000*1000);
首先,我们运行一个城市和国家表常规内连接:

mariadb5.3 <wbr>子查询优化图

 让我们把DuplicateWeedout插入图片:
mariadb5.3 <wbr>子查询优化图

 在最后图中的Start temporary 和 End temporary显示在EXPLAIN输出:

MariaDB [world]> explain select * from Country where Country.code IN (select City.Country from City where City.Population > 0.33 * Country.Population and City.Population > 1*1000*1000)\G

*************************** 1. row ***************************

           id: 1

  select_type: PRIMARY

        table: City

         type: range

possible_keys: Population,Country

          key: Population

      key_len: 4

          ref: NULL

         rows: 238

        Extra: Using index condition; Start temporary

*************************** 2. row ***************************

           id: 1

  select_type: PRIMARY

        table: Country

         type: eq_ref

possible_keys: PRIMARY

          key: PRIMARY

      key_len: 3

          ref: world.City.Country

         rows: 1

        Extra: Using where; End temporary

2 rows in set (0.00 sec)

这个查询将从city表读取238行,对于它们中的每一行将在country表做一个主键查找,扫描另一个238行。这总共476行,您需要添加238次在临时表查找(这通常是快很多自临时表在内存)。    

如果我们在MySQL运行相同的EXPLAIN,我们会得到:

mysql> explain select * from Country where Country.code IN (select City.Country from City where City.Population > 0.33 * Country.Population and City.Population > 1*1000*1000)

*************************** 1. row ***************************

           id: 1

  select_type: PRIMARY

        table: Country

         type: ALL

possible_keys: NULL

          key: NULL

      key_len: NULL

          ref: NULL

         rows: 239

        Extra: Using where

*************************** 2. row ***************************

           id: 2

  select_type: DEPENDENT SUBQUERY

        table: City

         type: index_subquery

possible_keys: Population,Country

          key: Country

      key_len: 3

          ref: func

         rows: 18

        Extra: Using where

2 rows in set (0.00 sec)

这个计划将读取(239 + 239 * 18)= 4541行,这是要慢得多。  

实况报道
在EXPLAIN DuplicateWeedout显示为"Start temporary/End temporary"。  
这个策略可以处理相关子查询。  
但它不能应用,如果子查询有有意义的GROUP BY和/聚合函数。  
DuplicateWeedout允许优化器自由混合子查询的表和外部选择的表。 
对DuplicateWeedout没有单独的@@optimizer_switch标志 DuplicateWeedout。该策略可以被禁用,通过关掉所有半连接的优化set @@optimizer_switch = ' optimizer_semijoin = off”命令

让我们用通常的例子,一个寻找有大城市的国家:

select * from Country

where Country.code IN (select City.Country from City

where City.Population > 1*1000*1000) and Country.cOntinent='Europe'

假设,我们的执行计划是找到在欧洲的国家,然后,对于每一个国家, 检查它发现有任何大的城市。常规内连接执行将如下所示:

mariadb5.3 <wbr>子查询优化图

既然德国有两个大的城市(在这个图中),它将被放入查询输出两次。这是不正确的, SELECT ... FROM CountrySELECT ... FROM Country不应该产生相同的国家记录两次。这个FirstMatch策略通过一旦找到第一个真正的匹配切断执行避免产生重复的行:

mariadb5.3 <wbr>子查询优化图

注意,短切必须发生在"Using where"被应用后。

FirstMatch in action

在上面的查询的EXPLAIN将如下所示:

MariaDB [world]> explain select * from Country  where Country.code IN (select City.Country from City where City.Population > 1*1000*1000) and Country.cOntinent='Europe';

+----+------+------+------+-----+---------+-------+---------+-+--------+

| id | select_type|table| type| possible_keys | key| key_len|ref|rows|Extra              |

+----+-----+------+------+--------+-------+---------+----------+------+-------+

1 |PRIMARY|Country|ref| PRIMARY,continent|continent | 17| const |60 | Using index condition |

1 | PRIMARY|City|ref| Population,Country|Country | 3| world.Country.Code |18 | Using where; FirstMatch(Country) |

+----+-----+----+------+-------+------+--------+-----------+------+-----------+

2 rows in set (0.00 sec)

FirstMatch(Country)在额外的列意味着一旦我们产生出了一条匹配记录组合,捷径执行和跳回到这个Country表。  FirstMatch的查询计划非常类似于一个你会在MySQL:

MySQL [world]> explain select * from Country  where Country.code IN (select City.Country from City where City.Population > 1*1000*1000) and Country.cOntinent='Europe';

+----+----+------+------+--------+-------+-----+-------+------+--------------+

| id |select_type|table| type|possible_keys | key| key_len | ref| rows | Extra                              |

+----+---+-------+-----+-----+-----------+-------+------+-----+---------------+

1 |PRIMARY|Country |ref| continent| continent | 17|const|60| Using index condition; Using where |

2 | DEPENDENT SUBQUERY | City|index_subquery|Population,Country|Country| 3       |func| 18| Using where |

+----+----------+---------+----------+--------+------+-----+------+------+----+

2 rows in set (0.01 sec)

和这两个特定的查询计划将执行在同一时间。

FirstMatch和IN->EXISTS之间的差异

FirstMatch策略背后的主要思想和IN->EXISTS转换的背后主要思想是,然而,FirstMatch有几个优势:
等效传值跨越半连接界限,但不是子查询界限。因此,将子查询转换到半连接和使用FirstMatch仍然可以给一个更好的执行计划。(TODO例子)     
只有一种方法可以应用IN->EXISTS策略和MySQL会无条件地这样做。对于FirstMatch,优化器可以做一个选择是否应该运行FirstMatch策略,一旦子查询所有使用表是在连接之前,或者在以后某个时间点。(TODO:示例)
 
FirstMatch
实况报道
FirstMatch的策略是通过执行子查询和一旦第一个找到匹配切断其执行。  
这意味着,子查询的表必须在所有的外部选择的表之后子查询谓词引用的。  
EXPLAIN显示FirstMatch为“FirstMatch(tableN)”。  
这个策略可以处理相关子查询。  
但它不能应用如果子查询有有意义的GROUP BY或聚合函数。  
使用FirstMatch策略由@@optimizer_switch中的firstmatch=on|off标志

LooseScan是对半连接子查询的一个执行策略。

思想

我们将通过示例演示LooseScan策略。假设,我们正在寻找有卫星的国家。我们可以让他们使用以下查询(为了简单起见,我们忽略多个国家组成拥有的卫星):

select * from Country 

where Country.code in (select country_code from Satellite)

假设,在Satellite.country_code有一个索引。如果我们使用该索引,我们会得到结果会按照拥有卫星的国家顺序:

mariadb5.3 <wbr>子查询优化图

LooseScan的策略并不真的需要排序,它所需要的只是分组。在上面的图,卫星是按国家分组。例如,所有属于澳大利亚的卫星在一起,没有和其他国家混合的卫星。这使得它容易从每组只选择一个卫星,您可以把它的国家和没有重复的国家名单进行连接:

mariadb5.3 <wbr>子查询优化图

LooseScan 的行动

对上面的查询EXPLAIN输出如下:

MariaDB [world]> explain select * from Country where Country.code in (select country_code from Satellite);

+----+-------+-------+------+---------+--------+-------+----------+------+---+

| id | select_type|table| type| possible_keys | key| key_len | ref| rows | Extra                          |

+----+-----+------+------+----+-------+------+------------+------+---------+

| 1 | PRIMARY| Satellite| index|country_code|country_code | 9| NULL|  932 | Using where; Using index; LooseScan |

1 | PRIMARY| Country|eq_ref |PRIMARY| PRIMARY| 3| world.Satellite.country_code |1 | Using index condition               |

+----+-----+-----+-----+--------+------+----+------------------

实况报道
LooseScan避免重复记录的组合产生通过将子查询表放在第一位和使用其索引从重复的行选择一行记录  
因此,为了适用LooseScan,子查询应该像:

expr IN (SELECT tbl.keypart1 FROM tbl ...)

expr IN (SELECT tbl.keypart2 FROM tbl WHERE tbl.keypart1=const AND ...)
LooseScan 能处理相关子查询
LooseScan 可以通过在@@optimizer_switch把loosescan=off标志关闭

5:Semi-join materialization strategy

半联接物化是一种特殊的子查询物化用于半连接子查询。它实际上包含两个策略:
Materialization/lookup
Materialization/scan

思想

考虑一个查询,查找在欧洲国家,有大的城市:

select * from Country

where Country.code IN (select City.Country

from City where City.Population > 7*1000*1000) and Country.cOntinent='Europe'

子查询是不相关的,也就是说,我们可以运行它独立于外部的查询。这就是半连接物化的想法,并用大城市的City.country填写临时表。然后用欧洲国家做一个连接:

mariadb5.3 <wbr>子查询优化图

join可以在两个方向进行:
从物化表到欧洲国家  
从欧洲国家到物化表

第一种方式涉及到做一个全扫描物化表,所以我们称之为“Materialization-scan”。    

如果你从国家到物化表运行一个连接,最有效的方法是在物化表使用一个查找其主键找到匹配(它有一个:我们用它来消除重复的数据)。正因为如此,我们称之策略为” Materialization-lookup”。

Semi-join materialization 行动

Materialization-Scan

如果我们选择寻找城市人口超过700万,优化器将使用Materialization-Scan和EXPLAIN将展示这个:

MariaDB [world]> explain select * from Country where Country.code IN (select City.Country from City where  City.Population > 7*1000*1000);

+----+-------+-------+-----+-------+-------+-------+-----------+------+-------+

| id | select_type| table  | type | possible_keys|key| key_len| ref| rows | Extra            |

+----+--------+-------+------+---------+--------+-------+---------+------+----+

1 | PRIMARY | | ALL| distinct_key| NULL| NULL| NULL| 15 |  

1 | PRIMARY |Country| eq_ref| PRIMARY| PRIMARY | 3|world.City.Country |1 |                       |

2 | MATERIALIZED | City| range| Population,Country | Population | 4| NULL|15 | Using index condition |

+----+-------+------+--------+--------+-------+-------+---------+------+------+

3 rows in set (0.01 sec)

在这里,你可以看到:
还有两个SELECTs (寻找列id = 1和id = 2)   
第二个选择(id = 2)有select_type=MATERIALIZED。这意味着它将被执行,其结果将存储在一个临时表和在所有列有一个唯一键。唯一索引是防止表包含任何重复的记录。  
第一个选择收到了表名& lt;subquery2>。这表是我们选择id = 2的物化得到的结果。

优化器选择做一个全扫描物化表,所以这是一个使用Materialization-Scan策略示例。    

至于执行成本,我们要从city表读15行,写15行到物化表,读他们回来(优化器假定不会有任何重复的),然后做15 eq ref访问Country表。总的来说,我们将做45次读和15次写。    

相比之下,如果你在MySQL运行EXPLAIN,您将得到这个:

MySQL [world]> explain select * from Country where Country.code IN (select City.Country from City where  City.Population > 7*1000*1000);

+----+--------------------+---------+-------+--------------------+------------+---------+------+------+------------------------------------+

| id | select_type        | table   | type  | possible_keys      | key        | key_len | ref  | rows | Extra                              |

+----+--------------------+---------+-------+--------------------+------------+---------+------+------+------------------------------------+

1 | PRIMARY            | Country | ALL   | NULL               | NULL       | NULL    | NULL |  239 | Using where                        |

2 | DEPENDENT SUBQUERY | City    | range | Population,Country | Population | 4       | NULL |   15 | Using index condition; Using where |

+----+--------------------+---------+-------+--------------------+------------+---------+------+------+------------------------------------+

..计划做 (239 + 239*15) = 3824次读表

Materialization-Lookup

让我们稍微修改查询,寻找国家城市人口超过一亿(而不是七):

MariaDB [world]> explain select * from Country where Country.code IN (select City.Country from City where  City.Population > 1*1000*1000) ;

+----+--------------+-------------+--------+--------------------+--------------+---------+------+------+-----------------------+

| id | select_type  | table       | type   | possible_keys      | key          | key_len | ref  | rows | Extra                 |

+----+--------------+-------------+--------+--------------------+--------------+---------+------+------+-----------------------+

1 | PRIMARY      | Country     | ALL    | PRIMARY            | NULL         | NULL    | NULL |  239 |                       |

1 | PRIMARY      | | eq_ref | distinct_key       | distinct_key | 3       | func |    1 |                       |

2 | MATERIALIZED | City        | range  | Population,Country | Population   | 4       | NULL |  238 | Using index condition |

+----+--------------+-------------+--------+--------------------+--------------+---------+------+------+-----------------------+

3 rows in set (0.00 sec)

EXPLAIN输出类似于使用Materialization-scan,除了:
the 表被访问使用 eq_ref 访问方法
访问使用的索引为distinct_key

这意味着优化器计划在物化表做索引查找。换句话说,我们要使用Materialization-lookup策略。    

在MySQL(或用optimizer_switch='semijoin=off,materialization=off'),你将得到这个EXPLAIN:

MySQL [world]> explain select * from Country where Country.code IN (select City.Country from City where  City.Population > 1*1000*1000) ;

+----+--------------------+---------+----------------+--------------------+---------+---------+------+------+-------------+

| id | select_type        | table   | type           | possible_keys      | key     | key_len | ref  | rows | Extra       |

+----+--------------------+---------+----------------+--------------------+---------+---------+------+------+-------------+

1 | PRIMARY            | Country | ALL            | NULL               | NULL    | NULL    | NULL |  239 | Using where |

2 | DEPENDENT SUBQUERY | City    | index_subquery | Population,Country | Country | 3       | func |   18 | Using where |

你可以看到这两个计划将做一个全扫描Country。第二步,MariaDB将填冲物化表(从表City读取238行和写入临时表),然后在Country做一个唯一索引查找,这做238唯一键查找。总的来说,第二步将成本(239 + 238)= 477 和238次写临时表。    

MySQL的计划,第二步是对于来自Country每条记录使用City.Country索引读18行。这是一个成本(18 * 239)= 4302次读操作。要是有更少的子查询调用,这个计划会比一个物化好点。顺便说一下,MariaDB也有一个选项来使用这样查询计划, (见FirstMatch策略),但是它并没有选择它。

有分组的子查询

MariaDB是能够使用半连接物化策略当子查询已分组(其他半连接策略并不适用于这种情况下)。    

这为了查询能高效执行,寻找最好的/最后一个元素在一个特定的组。    

例如,在他们的大洲找到人口最多的城市:

explain

select * from City

where City.Population in (select max(City.Population) from City, Country

where City.Country=Country.Code   group by Continent)

+------+--------------+-------------+------+---------------+------------+---------+----------------------------------+------+-----------------+

| id   | select_type  | table       | type | possible_keys | key        | key_len | ref                              | rows | Extra           |

+------+--------------+-------------+------+---------------+------------+---------+----------------------------------+------+-----------------+

   1 | PRIMARY      | | ALL  | distinct_key  | NULL       | NULL    | NULL                             239 |                 |

   1 | PRIMARY      | City        | ref  | Population    | Population | 4       | .max(City.Population) |    1 |                 |

   2 | MATERIALIZED | Country     | ALL  | PRIMARY       | NULL       | NULL    | NULL                             239 | Using temporary |

   2 | MATERIALIZED | City        | ref  | Country       | Country    | 3       | world.Country.Code                18 |                 |

+------+--------------+-------------+------+---------------+------------+---------+----------------------------------+------+-----------------+

4 rows in set (0.00 sec)

这个城市有:

+------+-------------------+---------+------------+

| ID   | Name              | Country | Population |

+------+-------------------+---------+------------+

| 1024 | Mumbai (Bombay)   | IND      10500000 |

| 3580 | Moscow            | RUS       8389200 |

| 2454 | Macao             | MAC        437500 |

608 | Cairo             | EGY       6789479 |

| 2515 | Ciudad de México | MEX       8591309 |

206 | So Paulo        | BRA       9968485 |

130 | Sydney            | AUS       3276207 |

+------+-------------------+---------+------------+

实况报道    

半连接物化
可用于不相关的IN子查询。子查询可以使用分组的和/或聚合函数。  
为子查询在EXPLAIN显示为type=MATERIALIZED和table=在父子查询。  
当在@@optimizer_switch启用materialization=on 和semijoin=on。  
materialization=on|off是与Non-semijoin materialization共享

Non-semi-join subquery optimizations(非半联接子查询优化)

哪些语句是非半联接查询?

IN子查询某些种类的子查询不能被简化成半连接。这些子查询可以同时相关或不相关的。为了在所有情况下提供一致的性能,MariaDB对于这些类型的子查询提供了几种可选策略。当有几个策略是有可能的,优化器基于成本的估计选择最佳的一个。非半联接的两个主要策略是物化 (也称由外向内的实体化),和in-to-exists的转换。物化仅适用于非相关的子查询,而in-to-exist可用于相关和不相关子查询。

 非半联接查询有以下模型:

1:子查询有or

SELECT ... FROM ... WHERE (expr1, ..., exprN) [NOT] IN (SELECT ... ) OR expr;

2:否定子查询谓词(not in)

SELECT ... FROM ... WHERE ... (expr1, ..., exprN) NOT IN (SELECT ... ) ...;

3:SELECT或HAVING子句中子查询

SELECT field1, ..., (SELECT ...)  WHERE ...;

SELECT ...  WHERE ... HAVING (SELECT ...);

4:有UNION的子查询
 ... [NOT] IN (SELECT ... UNION SELECT ...)

 优化策略

1:Materialization(仅对非相关子查询)

1.1什么是物化?

执行子查询和存储它的结果在一个内部临时表和所有选择的列加上索引。如果临时表的大小是小于系统变量tmp_table_size,这个表是一个hash索引内存堆表。子查询的结果超过这个限制,临时表存储在磁盘上的B -树索引表ARIA或MyISAM(ARIA是默认的)。

1.2:NULL-aware高效执行

(IN谓词会产生null结果,如果IN的任何一个参数为NULL ,(null in (select col )),col in(select null_column)).

在所有这些情况下,IN的评估是和前段所述一样,通过索引查找到物化子查询。在所有剩余的情况下不能用FALSE替换NULL,不可能使用索引查找。这不是在服务的上一个限制,但由于NULL语义适合ANSI SQL标准。    

假设一个IN谓词被判断为NULL IN(SELECT not_null_col from t1),即IN左操作数的是一个NULL值,并且在子查询没有NULL值。在这种情况下IN值既不FALSE,也不是TRUE。相反,它是NULL。如果我们在用NULL作为键执行索引查找,这样的值不会被发现在非NUL列,在谓词将错误地产生一个FALSE。

一般来说,一个NULL值在IN任何一方充当“通配符”,它可以匹配任何价值,如果一个匹配的存在,IN的结果是NULL的。考虑下面的例子:

如果左边的参数是行:(7,NULL,9),IN右边子查询结果是表:

(7, 8, 10)

(6, NULL, NULL)

(7, 11, 9)

IN谓词匹配的行(7,11,9),但结果是空的。在IN两边参数匹配不同的值是通过NULL配匹IN另一个参数,被称为部分匹配。    

为了有效地计算IN谓词出现在NULL的结I, 对部分匹配MariaDB实现了两个特殊算法,详细描述
Rowid-merge partial matching
这种技术时使用子查询结果的行数超过一定限度。这项技术在临时表一些列创建特殊的索引,并通过选择每个索引扫描将其合并因此执行操作类似于交集。
Table scan partial matching
该算法用于非常小的表时,rowid-merge算法开销过大不适合。然后服务简单地扫描物化的子查询表和检查部分匹配。因为这个策略不需要任何内存缓冲区,它也用于当没有足够的内存来保存rowid合并策略的索引。

局限性

原则上,子查询物化策略是通用的,然而,由于在MariaDB服务一些技术局限,服务器在下面情况不能应用这种优化。
BLOB字段
要么IN谓词的左操作数是指一个BLOB字段,要么子查询选择一个或更多BLOB字段。

在上述情况下,服务器返回到IN-TO-EXISTS转换。

IN到EXISTS的转换

这种优化是存在于MariaDB和MySQL到MariaDB 5.3之前的老版本唯一的子查询的执行策略。我们做了各种变化,并修复了在这段代码中许多bug,但在本质上仍然是相同的。    

这个优化眼下我们参考读物是MySQL文档的。

性能的讨论

速度超过MySQL 5.x和MariaDB 5.1/5.2事例

根据查询和数据,这里描述两种策略任一种可能导致数量级更好/更糟糕的计划比其他策略。    

老版本的MariaDB和任何当前MySQL版本(包括MySQL 5.5和MySQL 5.6 DMR截至2011年7月)实现只有IN-TO-EXISTS转换。如下图所示,这个策略在许多常见情况下不如子查询物化。    

考虑以下查询是在DBT3基准规模10的数据。查找在他们国家的顶级收益的顾客:

SELECT * FROM part WHERE p_partkey IN

 (SELECT l_partkey FROM lineitem

  WHERE l_shipdate between '1997-01-01' and '1997-02-01')

ORDER BY p_retailprice DESC LIMIT 10;

运行这个查询的时间如下:
在 MariaDB 5.2/MySQL 5.x (any MySQL)执行时间: > 1 h
查询花费超过一小时(我们不等待更长的时间),这使得在这种情况下使用子查询是不切实际。下面的EXPLAIN 表明,子查询被转换成一个相关子查询,表明IN-TO-EXISTS转换。

+--+------------+-------+-------+---------+----+------+----------------+

|id|select_type |table |type    |key       |ref |rows  |Extra           |

+--+---------+------+-----+--------+---+-----+----------------------------+

| 1|PRIMARY  |part |ALL  |NULL    |NULL|199755|Using where; Using filesort|

| 2|DEPENDENT SUBQUERY|lineitem|index_subquery|i_l_suppkey_partkey|func|14|Using where |

+--+-----+----+---------+------------+----+------+------------------------+
在MariaDB5.3执行时间: 43 sec
在MariaDB 5.3运行相同的查询只花了不到一分钟。Expalin 表明,子查询仍然是不相关的,这是一个迹象表明它是通过子查询物化执行饿。

+--+---------+--------+----+--------------+----+-----+---------------------+

|id|select_type|table |type|key         |ref |rows  |Extra                 |

+--+---------+------+------+-------------+----+------+---------------------+

| 1|PRIMARY  |part   |ALL   |NULL   |NULL|199755|Using temporary; Using filesort|

| 1|PRIMARY ||eq_ref|distinct_key |func|  1|                   |

| 2|MATERIALIZED|lineitem|range|l_shipdate_partkey|NULL|160060|Using where; Using index|

+--+-------+----------+------+---------+----+------+-------------------------+

这里的速度几乎是无限的,因为MySQL和老MariaDB版本不能在任何合理的时间完成查询。    

为了显示部分匹配的好处我们从DBT3基准用两个额外的列扩展客户表:
c_pref_nationkey -首选购买的国家,
c_pref_brand -首选品牌.

这两列被加上前缀百分比的NULL值,即,c_pref_nationkey_05包含NULL值有5%。    

考虑查询“找到在一些日期范围没有从一个首选的国家,从一个首选品牌购物所有客户”:

SELECT count(*)

FROM customer

WHERE (c_custkey, c_pref_nationkey_05, c_pref_brand_05) NOT IN

  (SELECT o_custkey, s_nationkey, p_brand

   FROM orders, supplier, part, lineitem

   WHERE l_orderkey = o_orderkey and l_suppkey = s_suppkey and l_partkey = p_partkey and p_retailprice <1200 and  l_shipdate >= '1996-04-01' and l_shipdate <'1996-04-05' and o_orderdate >= '1996-04-01' and o_orderdate <'1996-04-05');
在 MariaDB 5.2/MySQL 5.x (any MySQL)执行时间: 40 sec
在MariaDB执行时间: 2 sec

优化器控制开关

在某些情况下,可能需要覆盖选择的优化器。通常这是基准测试所需或测试目的,或者模仿行为的一个旧版本的服务器,或者如果优化器做了一个糟糕的选择。    所有上述策略可以通过以下开关控制优化器开关系统变量。
materialization=on/off
在一些非常特殊的情况下,即使物化被强制使用,优化器可能仍回归IN-TO-EXISTS策略如果物化没有使用。在这个情况下当物化要求部分匹配 (因为出现NULL值),有两个下属开关控制两部分匹配策略:
partial_match_rowid_merge=on/off
这个开关控制着Rowid-merge策略。除了这个开关,系统变量rowid_merge_buff_size控制Rowid-merge strategy最大可用的内存
partial_match_table_scan=on/off
控制可选部分匹配策略,通过一个表扫描执行匹配。
in_to_exists=on/off
这个开关控制着IN-TO-EXISTS转换.
系统变量tmp_table_size 和 max_heap_table_size
tmp_table_size 系统变量设置内存临时表最大的限制,如果一个内部临时表超过这个规模,它自动转换成一个磁盘上B-tree 索引的ARIA或MyISAM表。然而注意, 注意然而,内存表不能大于max_heap_table_size。

优化器两个主要的开关- materializationin_to_exists不能同时掉。如果双方都将关闭,服务器将发出一个错误。

 2:Subquery cache

子查询缓存的目标是优化相关子查询的估价,通过把结果和相关参数一起存储在一个缓存,避免重新执行的子查询,如果结果已经在缓存中。

管理

自从MariaDB 5 3 2 beta开始缓存是默认启用的。在早期MariaDB版本,缓存是默认关闭的。能把它打开或关闭使用优化器开关存:set optimizer_switch='subquery_cache=on';

在两个统计变量子查询缓存的效率是可见的:
subquery_cache_hit -全局计数器对于所有子查询缓存命中率统计
subquery_cache_miss -全局计数器对于所有子查询缓存未命中统计

会话变量tmp_table_size和max_heap_table_size都可以影响内存临时表的大小用于缓存。它不能超过上述变量中的最小值(了解详情见实现部分)。

可见性

你使用的缓存时在EXPLAIN EXTENDED输出是可见的(警告)是这样: "(//cached expression//)"。例如:

MariaDB [test]> explain extended select * from t1 where a in (select b from t2);

+----+----------+-------+------+------+------+---------+------+------+----+---+

| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra  |

+----+--------+----+-----+------+------+------+------+--+-----+---------------+

1 | PRIMARY | t1  | ALL  | NULL | NULL | NULL| NULL  2 | 100.00 |Using where |

2 | DEPENDENT SUBQUERY | t2 | ALL| NULL   | NULL | NULL  | NULL | 2 | 100.00 | Using where |

+----+--------+-------+------+---------+------+------+------+------+-------+--+

2 rows in set, 1 warning (0.00 sec)

 MariaDB [test]> show warnings;

+-------+------+--------------------------------------------------------------+

| Level | Code | Message                                                                                                                                                                                                    |

+-------+------+--------------------------------------------------------------+

| Note  | 1003 | select `test`.`t1`.`a` AS `a` from `test`.`t1` where <`test`.`t1`.`a`>((`test`.`t1`.`a`,(select 1 from `test`.`t2` where ((`test`.`t1`.`a`) = `test`.`t2`.`b`)))) |

+-------+------+--------------------------------------------------------------+

1 row in set (0.00 sec)

在上面的示例中"<`test`.`t1`.`a`>(...)"是你知道你如何正在使用子查询缓存。

实现

每个子查询缓存创建一个临时表,结果和所有参数都存储。在所有参数,它有一个唯一索引。首先缓存是在堆表中创建的 (如果不能这样做,缓存就不会缓存这个表达式)。当cache表的大小到达到tmp_table_size和max_heap_table_size,命中率将检查:
如果命中率是很小(<0.2)缓存将被禁用
如果命中率是温和的(<0.7)cache表将被清洗(删除所有记录)保持表在内存中
如果命中率高,表将会被转化为一个磁盘表(对于5 .3. 0它只能被转换为一个磁盘表)。
hit rate = hit / (hit + miss)


推荐阅读
  • 本文深入探讨了MySQL中常见的面试问题,包括事务隔离级别、存储引擎选择、索引结构及优化等关键知识点。通过详细解析,帮助读者在面对BAT等大厂面试时更加从容。 ... [详细]
  • MySQL Debug 模式的实现与应用
    本文详细介绍了如何启用和使用 MySQL 的调试模式,包括编译选项、环境变量配置以及调试信息的解析。通过实际案例展示了如何利用调试模式解决客户端无法连接服务器的问题。 ... [详细]
  • 如何从python读取sql[mysql基础教程]
    从python读取sql的方法:1、利用python内置的open函数读入sql文件;2、利用第三方库pymysql中的connect函数连接mysql服务器;3、利用第三方库pa ... [详细]
  • centos 7.0 lnmp成功安装过程(很乱)
    下载nginx[rootlocalhostsrc]#wgethttp:nginx.orgdownloadnginx-1.7.9.tar.gz--2015-01-2412:55:2 ... [详细]
  • 在重新安装Ubuntu并配置Django和PyCharm后,忘记测试MySQL连接,导致在后续配置过程中遇到错误:ERROR 2003 (HY000) - 无法连接到本地服务器 ‘127.0.0.1’ (111)。本文将详细介绍该错误的原因及解决步骤,帮助用户快速恢复MySQL服务的正常运行。我们将从检查网络配置、验证MySQL服务状态、配置防火墙规则等方面入手,提供全面的故障排除指南。 ... [详细]
  • 如何在Linux系统上部署MySQL 5.7.28
    本文详细介绍了在Linux系统上部署MySQL 5.7.28的具体步骤。通过官方下载页面获取最新安装包后,按照提供的指南进行配置和安装。文章内容实用性强,适合初学者和有经验的管理员参考。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了实现MySQL半同步架构相关的知识,希望对你有一定的参考价值。默认情况下,MySQL的复制功能是异步的, ... [详细]
  • 深入解析ESFramework中的AgileTcp组件
    本文详细介绍了ESFramework框架中AgileTcp组件的设计与实现。AgileTcp是ESFramework提供的ITcp接口的高效实现,旨在优化TCP通信的性能和结构清晰度。 ... [详细]
  • 本文详细介绍了 phpMyAdmin 的安装与配置方法,适用于多个版本的 phpMyAdmin。通过本教程,您将掌握从下载到部署的完整流程,并了解如何根据不同的环境进行必要的配置调整。 ... [详细]
  • MySQL 'Too Many Connections' 错误处理及优化方案
    本文详细介绍了如何诊断和解决MySQL数据库中出现的‘Too Many Connections’错误,包括查看当前连接状态、调整配置文件以及优化应用代码等方法。 ... [详细]
  • 本文详细介绍了MySQL中的存储过程,包括其定义、优势与劣势,并提供了创建、调用及删除存储过程的具体示例,旨在帮助开发者更好地利用这一数据库特性。 ... [详细]
  • 在关系型数据库中,数据约束是指在向数据表中插入数据时必须遵循的限制条件。在MySQL和MariaDB中,常见的数据约束包括主键约束、唯一键约束、外键约束以及非空约束等。这些约束确保了数据的完整性和一致性,是数据库管理中的重要组成部分。通过合理设置和使用这些约束,可以有效防止数据冗余和错误,提升数据库的可靠性和性能。 ... [详细]
  • MySQL 8.0 MGR 自动化部署与配置:DBA 和开源工具的高效解决方案
    MySQL 8.0 MGR 自动化部署与配置:DBA 和开源工具的高效解决方案 ... [详细]
  • 如何利用Apache与Nginx高效实现动静态内容分离
    如何利用Apache与Nginx高效实现动静态内容分离 ... [详细]
  • 在使用 PHP 通过 SSL 安全连接到 MySQLi 数据库服务器时,遇到了一些技术难题。我的环境包括一个 Web 服务器和一个数据库服务器,两者均使用 OpenSSL 生成了证书。尽管证书内容一致,但在尝试从 Web 服务器使用 `mysql` 命令进行连接时,仍然遇到了问题。为了确保连接的安全性和稳定性,需要进一步检查证书配置和 PHP 的 SSL 设置,以排除潜在的配置错误或兼容性问题。 ... [详细]
author-avatar
陈翔瑋文爱奕德
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有