MySQL查询性能非常慢

时间:2018-11-20 23:11:50

标签: mysql query-performance mysql-5.7

我完成一个查询大约需要 18秒

查询:

SELECT YEAR(c.date), MONTH(c.date), p.district_id, COUNT(p.owner_id)
FROM commission c
  INNER JOIN partner p ON c.customer_id = p.id
WHERE (c.date BETWEEN '2018-01-01' AND '2018-12-31')
  AND (c.company_id = 90)
  AND (c.source = 'ACTUAL')
  AND (p.id IN (3062, 3063, 3064, 3065, 3066, 3067, 3068, 3069, 3070, 3071,
    3072, 3073, 3074, 3075, 3076, 3077, 3078, 3079, 3081, 3082, 3083, 3084,
    3085, 3086, 3087, 3088, 3089, 3090, 3091, 3092, 3093, 3094, 3095, 3096,
    3097, 3098, 3099, 3448, 3449, 3450, 3451, 3452, 3453, 3454, 3455, 3456,
    3457, 3458, 3459, 3460, 3461, 3471, 3490, 3491, 6307, 6368, 6421))
  GROUP BY YEAR(c.date), MONTH(c.date), p.district_id

commission表大约有 280万条记录,其中 860 000 + 属于当年2018年。{{1} }表目前有8600多个记录。

结果

partner

说明:

| `YEAR(c.date)` | `MONTH(c.date)` | district_id | `COUNT(c.id)` | 
|----------------|-----------------|-------------|---------------| 
| 2018           | 1               | 1           | 19154         | 
| 2018           | 1               | 5           | 9184          | 
| 2018           | 1               | 6           | 2706          | 
| 2018           | 1               | 12          | 36296         | 
| 2018           | 1               | 15          | 13085         | 
| 2018           | 2               | 1           | 21231         | 
| 2018           | 2               | 5           | 10242         | 
| ...            | ...             | ...         | ...           | 

55 rows retrieved starting from 1 in 18 s 374 ms 
(execution: 18 s 368 ms, fetching: 6 ms)

DDL:

| id | select_type | table | partitions | type  | possible_keys                                                                                        | key                  | key_len | ref             | rows | filtered | extra                                        | 
|----|-------------|-------|------------|-------|------------------------------------------------------------------------------------------------------|----------------------|---------|-----------------|------|----------|----------------------------------------------| 
| 1  | SIMPLE      | p     | null       | range | PRIMARY                                                                                              | PRIMARY              | 4       |                 | 57   | 100      | Using where; Using temporary; Using filesort | 
| 1  | SIMPLE      | c     | null       | ref   | UNIQ_6F7146F0979B1AD62FC0CB0F5F8A7F73,IDX_6F7146F09395C3F3,IDX_6F7146F0979B1AD6,IDX_6F7146F0AA9E377A | IDX_6F7146F09395C3F3 | 5       | p.id            | 6716 | 8.33     | Using where                                  | 

我注意到,通过删除伙伴create table if not exists commission ( id int auto_increment primary key, date date not null, source enum('ACTUAL', 'EXPECTED') not null, customer_id int null, transaction_id varchar(255) not null, company_id int null, constraint UNIQ_6F7146F0979B1AD62FC0CB0F5F8A7F73 unique (company_id, transaction_id, source), constraint FK_6F7146F09395C3F3 foreign key (customer_id) references partner (id), constraint FK_6F7146F0979B1AD6 foreign key (company_id) references companies (id) ) collate=utf8_unicode_ci; create index IDX_6F7146F09395C3F3 on commission (customer_id); create index IDX_6F7146F0979B1AD6 on commission (company_id); create index IDX_6F7146F0AA9E377A on commission (date); 条件,MySQL仅花费3s。我试图用类似的疯狂方式替换它:

IN

结果大约是5秒...太棒了!但这是黑客。

为什么,当我使用AND (',3062,3063,3064,3065,3066,3067,3068,3069,3070,3071,3072,3073,3074,3075,3076,3077,3078,3079,3081,3082,3083,3084,3085,3086,3087,3088,3089,3090,3091,3092,3093,3094,3095,3096,3097,3098,3099,3448,3449,3450,3451,3452,3453,3454,3455,3456,3457,3458,3459,3460,3461,3471,3490,3491,6307,6368,6421,' LIKE CONCAT('%,', p.id, ',%')) 语句时,此查询执行时间很长吗?解决方法,提示,链接等。谢谢!

3 个答案:

答案 0 :(得分:2)

MySQL一次只能使用一个索引。对于此查询,您需要一个涵盖搜索内容的复合索引。应该在范围方面使用WHERE子句的常量方面,例如:

public static String RemoveLastNotSpaceChar(String str){
      if(str==null)
          return null;
      str = removeTrailingSpaces(str);
      if(str.length()>0)
          return str.substring(0, str.length() - 1);
      return "";
}

public static String removeTrailingSpaces(String param) 
    {
        if (param == null)
            return null;
        int len = param.length();
        for (; len > 0; len--) {
            if (!Character.isWhitespace(param.charAt(len - 1)))
                break;
        }
        return param.substring(0, len);
    }

答案 1 :(得分:1)

这是优化程序在查询中看到的内容。

检查是否对GROUP BY使用索引:

  • YEAR()中的函数(GROUP BY),所以没有。
  • 提到了多个表(cp),所以没有。

对于JOIN,Optimizer(几乎总是)从一个开始,然后进入另一个。因此,让我们看一下两个选项:

如果p开始

假设您有PRIMARY KEY(id),没有什么要考虑的。它将仅使用该索引。

对于从p中选择的每一行,它将查看c,并且此INDEX的任何变化都是最佳的。

c: INDEX(company_id, source, customer_id,  -- in any order (all are tested "=")
         date)       -- last, since it is tested as a range

如果c开始

c: INDEX(company_id, source,  -- in any order (all are tested "=")
         date)       -- last, since it is tested as a range
-- slightly better:
c: INDEX(company_id, source,  -- in any order (all are tested "=")
         date,       -- last, since it is tested as a range
         customer_id)  -- really last -- added only to make it "covering".

优化器将查看“统计信息”以粗略地决定从哪个表开始。因此,添加我建议的所有索引。

“覆盖”索引是一个包含 all 查询中任何地方 的列的索引。有时,用更多列扩展“好”索引以使其“覆盖”是明智的。

但是这里有一个活动扳手。 c.customer_id = p.id表示customer_id IN (...)有效存在。但是现在有两个“范围类似”约束-一个是IN,另一个是“范围”。在某些较新的版本中,由于IN still 能够进行“范围”扫描,因此Optimizer会很高兴跳来跳去。因此,我建议使用以下顺序:

  1. column = constant的测试
  2. 使用IN进行测试
  3. 一个“范围”测试({BETWEEN>=LIKE带有尾随通配符,等等)
  4. 也许添加更多列以使其“覆盖”-但是如果索引中的列超过5个,则不要执行此步骤。

因此,对于c,以下条件对于WHERE是最佳的,并且恰好是“覆盖”。

INDEX(company_id, source,  -- first, but in any order (all "=")
      customer_id,  -- "IN"
      date)       -- last, since it is tested as a range

p: (same as above)

由于存在IN或“范围”,因此查看索引是否也可以处理GROUP BY毫无用处。

关于COUNT(x)的注释-检查x是否为NOT NULL。通常说COUNT(*)是正确的,id可以计算行数,而无需任何额外的检查。

这不是入门,因为它在函数中隐藏了索引列(AND (',3062,3063,3064,3065,3066,...6368,6421,' LIKE CONCAT('%,', p.id, ',%')) ):

{{1}}

答案 2 :(得分:0)

对于您的LIKE黑客,您正在欺骗优化程序,因此它使用了不同的计划(最有可能首先使用IDX_6F7146F0AA9E377A索引)。 您应该能够在说明中看到这一点。

我认为您遇到的真正问题是解释的第二行:服务器对6716行执行多个功能(MONTH,YEAR),然后尝试对所有这些行进行分组。在这段时间内,所有这6716行都应存储在(基于服务器配置的内存或磁盘中)。

SELECT COUNT(*) FROM commission WHERE (date BETWEEN '2018-01-01' AND '2018-12-31') AND company_id = 90 AND source = 'ACTUAL';

=>我们正在谈论多少行?

如果上述查询中的数字远低于6716,我将尝试在customer_id,company_id,来源和日期列上添加覆盖索引。不确定最佳顺序,因为它取决于您拥有的数据(请检查这些列的基数)。我从索引(日期,company_id,来源,customer_id)开始。另外,我还要在合作伙伴上添加唯一索引(id,district_id,owner_id)。

还可以添加其他已生成的已存储列_year和_month(如果您的服务器有点旧,则可以添加普通列并用触发器填充它们)来摆脱多种功能的执行。