IN谓词转子查询
操作场景
社区MySQL在处理column IN (const1, const2, .... )时,如果column上有索引,那么通常优化器会选择Range scan进行扫描。在进行Range scan分析时,如果IN后面的列表非常大,使用的内存会超过range_optimizer_max_mem_size定义的最大内存,导致Range scan失效,从而引发查询的性能下降。
为了解决该问题,通常有以下几种方案,但每种方案都存在一定的弊端:
- 可以增加允许使用的最大内存,但是该内存是Session级别的,即每个Session进行该查询都会占用同样的内存,可能导致用户实例发生OOM。
- 如果允许执行 range optimizer,但是IN值过多超过eq_range_index_dive_limit限制,会导致无法进行index dive,而只能走索引统计信息。由于大量Value所对应的统计信息比较简单,很有可能出现估算不准确的情况,导致性能回退。
因此,TaurusDB支持IN谓词转子查询功能。对于满足前提条件的复杂查询,通过该功能优化器可以将某些大的IN谓词转换为IN子查询,随后优化器会进一步考虑是否将其转换为semijoin,以提升复杂查询的执行性能。
转换过程
原查询:
select ... from lineitem where l_partkey in (...)
转换后的查询:
select ... from lineitem where l_partkey in (select tb._col_1 from (values (9628136),(19958441),...) tb)
前提条件
- TaurusDB内核版本为2.0.42.230600或以上的版本。内核版本的查询方法请参见如何查看云数据库 TaurusDB实例的版本号。
- IN列表中的元素个数超过rds_in_predicate_conversion_threshold参数设置的个数。
支持的查询语句
- SELECT
- INSERT ... SELECT
- REPLACE ... SELECT
- 支持视图,PREPARED STMT
约束限制
- 只支持常量IN LIST(包括NOW()等不涉及表查询的语句)。
- 不支持STORED PROCEDURE/FUNCTION/TRIGGER。
- 不支持NOT IN,以及无法使用索引的场景。
使用方法
您可以通过“rds_in_predicate_conversion_threshold”参数设置IN谓词转子查询功能。
|
参数名称 |
级别 |
描述 |
|---|---|---|
|
rds_in_predicate_conversion_threshold |
Global |
IN谓词转子查询功能控制开关。当SQL语句的IN列表中的元素个数超过该参数的取值时,则SQL语句进行转换,将IN谓词转换为子查询。 默认为0,表示关闭该功能。 |
示例
explain select * from t where a in (1,2,3,4,5); +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | t | NULL | ALL | idx1 | NULL | NULL | NULL | 5 | 100.00 | Using where | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
explain format=tree select * from t where a in (1,2,3,4,5);
+-------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------------------------------------------------+
| -> Filter: (t.a in (1,2,3,4,5)) (cost=0.75 rows=5)
-> Table scan on t (cost=0.75 rows=5)
|
+-------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)
set rds_in_predicate_conversion_threshold=3; Query OK, 0 rows affected (0.00 sec)
explain select * from t where a in (1,2,3,4,5); +----+-------------+------------------+------------+--------+---------------------+---------------------+---------+----------+------+---- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+------------------+------------+--------+---------------------+---------------------+---------+----------+------+----| 1 | SIMPLE | t | NULL | ALL | idx1 | NULL | NULL | NULL | 5 | 100.00 | Using where | | 1 | SIMPLE | <in_predicate_2> | NULL | eq_ref | <auto_distinct_key> | <auto_distinct_key> | 5 | test.t.a | 1 | 100.00 | IN-list converted | +----+-------------+------------------+------------+--------+---------------------+---------------------+---------+----------+------+---- 2 rows in set, 1 warning (0.00 sec)
explain format=tree select * from t where a in (1,2,3,4,5);
+----------------------------------------------------------------------------------------------------------------------------------------
| EXPLAIN +----------------------------------------------------------------------------------------------------------------------------------------| -> Nested loop inner join (cost=2.50 rows=5)
-> Filter: (t.a is not null) (cost=0.75 rows=5)
-> Table scan on t (cost=0.75 rows=5)
-> Single-row index lookup on <in_predicate_2> using <auto_distinct_key> (a=t.a) (cost=0.27 rows=1)
|
使用explain,在执行计划中的table列存在<in_predicate_*>(*为数字),该表即为构造的临时表,其中存储了IN查询中的所有数据。
也可以通过查看optimize trace,trace中存在in_to_subquery_conversion相关信息。
| explain format=tree select * from t where a in (1,2,3,4,5) | {
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
"IN_uses_bisection": true
},
{
"in_to_subquery_conversion": {
"item": "(`t`.`a` in (1,2,3,4,5))",
"steps": [
{
"creating_tmp_table": {
"tmp_table_info": {
"table": "intermediate_tmp_table",
"columns": 1,
"row_length": 5,
"key_length": 5,
"unique_constraint": false,
"makes_grouped_rows": false,
"cannot_insert_duplicates": true,
"location": "TempTable"
}
}
},
性能测试
使用sysbench模型测试。
- 准备1000w数据。
sysbench /usr/share/sysbench/oltp_read_only.lua --tables=1 --report-interval=10 --table-size=10000000 --mysql-user=root --mysql-password=123456 --mysql-host=127.0.0.1 --mysql-port=3306 --mysql-db=sbtest --time=300 --max-requests=0 --threads=200 prepare
- 查询带1万个IN。
select count(*) from sbtest1 where id/k in (... ...);
性能对比如下表所示:
|
测试方法 |
开启转换 |
关闭转换(不适用range_opt) |
性能对比 |
|---|---|---|---|
|
带索引 |
0.09秒 |
2.48秒 |
提升26.56倍 |