提高自我JOIN SQL查询性能

时间:2016-10-24 16:08:55

标签: mysql performance mariadb

我尝试使用MariaDB 10.1.18(Linux Debian Jessie)来提高SQL查询的性能。

服务器有大量RAM(192GB)和SSD磁盘。

真实表有数亿行,但我可以在一部分数据和简化的布局上重现我的性能问题。

这是(简化的)表定义:

CREATE TABLE `data` (
  `uri` varchar(255) NOT NULL,
  `category` tinyint(4) NOT NULL,
  `value` varchar(255) NOT NULL,
  PRIMARY KEY (`uri`,`category`),
  KEY `cvu` (`category`,`value`,`uri`),
  KEY `cu` (`category`,`uri`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

为了重现我的内容的实际分布,我插入了大约200&000,000行(bash脚本):

#!/bin/bash
for i in `seq 1 100000`;
do
  mysql mydb -e "INSERT INTO data (uri, category, value) VALUES ('uri${i}', 1, 'foo');"
done

for i in `seq 99981 200000`;
do
  mysql mydb -e "INSERT INTO data (uri, category, value) VALUES ('uri${i}', 2, '$(($i % 5))');"
done

所以,我们插入:

  • 类别1中的100� 000行,带有静态字符串(" foo")作为值
  • 类别2中的100&000,000行,数字介于1和5之间作为值
  • 20行有一个共同的" uri"每个数据集(类别1/2)
  • 之间

我总是在查询之前运行一个ANALYZE TABLE。

以下是我运行的查询的解释输出:

MariaDB [mydb]> EXPLAIN EXTENDED
    -> SELECT d2.uri, d2.value
    -> FROM data as d1
    -> INNER JOIN data as d2 ON d1.uri  = d2.uri AND d2.category = 2
    -> WHERE d1.category = 1 and d1.value  = 'foo';
+------+-------------+-------+--------+----------------+---------+---------+-------------------+-------+----------+-------------+
| id   | select_type | table | type   | possible_keys  | key     | key_len | ref               | rows  | filtered | Extra       |
+------+-------------+-------+--------+----------------+---------+---------+-------------------+-------+----------+-------------+
|    1 | SIMPLE      | d1    | ref    | PRIMARY,cvu,cu | cu      | 1       | const             | 92964 |   100.00 | Using where |
|    1 | SIMPLE      | d2    | eq_ref | PRIMARY,cvu,cu | PRIMARY | 768     | mydb.d1.uri,const |     1 |   100.00 |             |
+------+-------------+-------+--------+----------------+---------+---------+-------------------+-------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

MariaDB [mydb]> SHOW WARNINGS;
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                              |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | select `mydb`.`d2`.`uri` AS `uri`,`mydb`.`d2`.`value` AS `value` from `mydb`.`data` `d1` join `mydb`.`data` `d2` where ((`mydb`.`d1`.`category` = 1) and (`mydb`.`d2`.`uri` = `mydb`.`d1`.`uri`) and (`mydb`.`d2`.`category` = 2) and (`mydb`.`d1`.`value` = 'foo')) |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

MariaDB [mydb]> SELECT d2.uri, d2.value FROM data as d1 INNER JOIN data as d2 ON d1.uri  = d2.uri AND d2.category = 2 WHERE d1.category = 1 and d1.value  = 'foo';
+-----------+-------+
| uri       | value |
+-----------+-------+
| uri100000 | 0     |
| uri99981  | 1     |
| uri99982  | 2     |
| uri99983  | 3     |
| uri99984  | 4     |
| uri99985  | 0     |
| uri99986  | 1     |
| uri99987  | 2     |
| uri99988  | 3     |
| uri99989  | 4     |
| uri99990  | 0     |
| uri99991  | 1     |
| uri99992  | 2     |
| uri99993  | 3     |
| uri99994  | 4     |
| uri99995  | 0     |
| uri99996  | 1     |
| uri99997  | 2     |
| uri99998  | 3     |
| uri99999  | 4     |
+-----------+-------+
20 rows in set (0.35 sec)

此查询在~350ms内返回20行。

对我来说似乎很慢。

有没有办法提高此类查询的效果?有什么建议吗?

2 个答案:

答案 0 :(得分:1)

您可以尝试以下查询吗?

  SELECT dd.uri, max(case when dd.category=2 then dd.value end) v2
    FROM data as dd
   GROUP by 1 
  having max(case when dd.category=1 then dd.value end)='foo' and v2 is not null;

我目前无法重复您的测试,但我希望只需扫描一次表就可以补偿聚合函数的使用。

被修改

创建了一个测试环境并测试了一些假设。 截至今天,最佳性能(100万行)一直是:

1 - 在uri列上添加索引

2 - 使用以下查询

 select d2.uri, d2.value 
   FROM data as d2 
  where exists (select 1 
                  from data d1 
                 where d1.uri  = d2.uri 
                   AND d1.category = 1 
                   and d1.value='foo') 
    and d2.category=2 
    and d2.uri in (select uri from data group by 1 having count(*) > 1);
具有讽刺意味的是,在第一个提案中,我试图尽量减少对表格的访问,现在我提议进行三次访问。

编辑:30/10

好的,所以我做了一些其他的实验,我想总结一下结果。 首先,我想扩展一下Aruna的答案: 我在OP问题中发现的有趣之处在于它是经典"经验法则的例外。在数据库优化中:如果所需结果的#与所涉及的表的维度相比非常小,则应该可以使用正确的索引来获得非常好的性能。

为什么我们不能简单地添加一个&#34;魔法索引&#34;拥有我们的20行?因为我们没有任何明确的攻击向量&#34; ...我的意思是,我们没有明确的选择标准可以应用于记录,以显着减少目标行的数量。< / p>

想一想:价值必须是&#34; foo&#34;只是从表中删除表的50%。此类别根本没有选择性:唯一感兴趣的是,对于20 uri,它们同时出现在类别1和2的记录中。

但问题在于:问题涉及比较两行,不幸的是,据我所知,索引(甚至不是基于Oracle函数的索引)也无法减少依赖于信息的条件多行。

结论可能是:如果您需要这些查询,则应修改数据模型。例如,如果你有一个有限的和少量的类别(让我们说三个=,你的表可能写成:

uri,value_category1,value_category2,value_category3

查询将是:

选择uri,value_category2  其中value_category1 =&#39; foo&#39;和value_category2不为空;

顺便说一句,让我们回到最初的问题。 我创建了一个稍微高效的测试数据生成器(http://pastebin.com/DP8Uaj2t)。

我已使用此表:

 use mydb;
 DROP TABLE IF EXISTS data2;

 CREATE TABLE data2 
 ( 
  uri varchar(255) NOT NULL, 
  category tinyint(4) NOT NULL, 
  value varchar(255) NOT NULL, 
  PRIMARY KEY (uri,category), 
  KEY cvu (category,value,uri), 
  KEY ucv (uri,category,value), 
  KEY u (uri), 
  KEY cu (category,uri)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

结果是:

 +--------------------------+----------+----------+----------+
 | query_descr              | num_rows | num      | num_test |
 +--------------------------+----------+----------+----------+
 | exists_plus_perimeter    |    10000 |   0.0000 |        5 |
 | exists_plus_perimeter    |    50000 |   0.0000 |        5 |
 | exists_plus_perimeter    |   100000 |   0.0000 |        5 |
 | exists_plus_perimeter    |   500000 |   2.0000 |        5 |
 | exists_plus_perimeter    |  1000000 |   4.8000 |        5 |
 | exists_plus_perimeter    |  5000000 |  26.7500 |        8 |
 | max_based                |    10000 |   0.0000 |        5 |
 | max_based                |    50000 |   0.0000 |        5 |
 | max_based                |   100000 |   0.0000 |        5 |
 | max_based                |   500000 |   3.2000 |        5 |
 | max_based                |  1000000 |   7.0000 |        5 |
 | max_based                |  5000000 |  49.5000 |        8 |
 | max_based_with_ucv       |    10000 |   0.0000 |        5 |
 | max_based_with_ucv       |    50000 |   0.0000 |        5 |
 | max_based_with_ucv       |   100000 |   0.0000 |        5 |
 | max_based_with_ucv       |   500000 |   2.6000 |        5 |
 | max_based_with_ucv       |  1000000 |   7.0000 |        5 |
 | max_based_with_ucv       |  5000000 |  36.3750 |        8 |
 | standard_join            |    10000 |   0.0000 |        5 |
 | standard_join            |    50000 |   0.4000 |        5 |
 | standard_join            |   100000 |   2.4000 |        5 |
 | standard_join            |   500000 |  13.4000 |        5 |
 | standard_join            |  1000000 |  33.2000 |        5 |
 | standard_join            |  5000000 | 205.2500 |        8 |
 | standard_join_plus_perim |  5000000 | 155.0000 |        2 |
 +--------------------------+----------+----------+----------+

使用的查询是: - query_max_based_with_ucv.sql
- query_exists_plus_perimeter.sql
- query_max_based.sql
- query_max_based_with_ucv.sql
- query_standard_join_plus_perim.sql query_standard_join.sql

最好的查询仍然是我在第一次环境创建后提出的&#34; query_exists_plus_perimeter&#34;

答案 1 :(得分:0)

主要是由于分析的行数。即使您有表格索引主要决策条件&#34; WHERE d1.category = 1和d1.value =&#39; foo&#39;&#34;过滤大量行

root

它必须再次读取表中的每个匹配行,这对于类别2.由于它在主键上读取,它可以直接获得匹配的行。

在原始表格中,检查类别和值组合的基数。如果它更加独特,您可以在(类别,值)上添加索引,这应该可以提高性能。如果它与给出的示例相同,则可能无法获得任何性能提升。