对于类似的查询,MySQL选择索引降级的性能

时间:2017-09-29 18:17:01

标签: mysql query-optimization

我有一个架构

applicants - id, max_res_id, max_visa_id
applicant_files - id, applicatid, fileid, filetype
files - id, name, filetype

申请人 -

CREATE TABLE `applicants` (
  `id` char(36) NOT NULL,
  `max_res_id` char(36) NOT NULL,
  `max_visa_id` char(36) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_res_id` (`max_res_id`) USING BTREE,
  KEY `idx_visa_id` (`max_visa_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

applicant_files

CREATE TABLE `applicant_files` (
  `id` char(36) CHARACTER SET latin1 NOT NULL,
  `applicantid` char(36) CHARACTER SET latin1 DEFAULT NULL,
  `fileid` char(36) CHARACTER SET latin1 DEFAULT NULL,
  `filetype` tinyint(1) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `q_applicantfile_fileid` (`fileid`),
  KEY `u_applicantfile_applid` (`applicantid`),
  KEY `idx_filetype` (`filetype`) USING BTREE,
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

文件

CREATE TABLE `files` (
  `id` char(36) NOT NULL,
  `filetype` tinyint(1) NOT NULL,
  `name` text,
  PRIMARY KEY (`id`),
  KEY `idx_filetype` (`filetype`) USING BTREE,
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

max_res_id, max_visa_id引用" id" applicant_filesfileid指的是" id" files

现在我有两个不同的查询 -

select  f.id as resumeId, f.name as resumeName, f.date_entered as resumeDate,
        a.id as applId
    from  oepl_applicants a
    inner join  applicant_files af
              ON ( a.id in ('id1', 'id2')
              and  a.id = af.applicantid
              and  a.max_res_id = af.id
              and  af.filetype = 1
              and  a.max_res_id != ''
              and  a.max_res_id is not null )
    inner join  files f
              ON ( af.fileid = f.id
              and  f.filetype = 1 ) 
select  f.id as visaId, f.name as visaName, f.date_entered as visaDate,
        a.id as applId
    from  oepl_applicants a
    inner join  applicant_files af 
              ON ( a.id in ('id1', 'id2')
              and  a.id = af.applicantid
              and  a.max_visa_id = af.id
              and  af.filetype = 2
              and  a.max_visa_id != ''
              and  a.max_visa_id is not null )
    inner join  files f 
              ON ( af.fileid = f.id
              and  f.filetype = 2 )

对于200个ID(id1,id2,... id200),第一个查询在2秒内返回结果,而第二个查询在30秒内返回结果。

这里可能出现什么问题?

这两个查询的唯一区别是文件类型不同,并且连接在2个不同的列上。 PS - 与max_res_id中的值相比,max_visa_id中的很多值都为null(空)

1 个答案:

答案 0 :(得分:1)

感谢您CREATE TABLEs

加入latin1 vs utf8无效使用索引!

虽然在这种情况下无关紧要,但请将“过滤”子句移到WHERE子句中,并在ON子句中仅保留描述表如何相关的子句。例如,在第一个查询中:

    inner join  applicant_files af
              ON   a.id = af.applicantid
              and  a.max_res_id = af.id
    inner join  files f  ON  af.fileid = f.id
            WHERE  a.id in ('id1', 'id2')
              and  f.filetype = 1
              and  af.filetype = 1
              and  a.max_res_id != ''
              and  a.max_res_id is not null

优化程序将决定查看表格的顺序。从“过滤”条款中,它希望看到这些:

a:        INDEX(max_res_id, id)
af and f: INDEX(filetype)  -- but see note below

然后优化器会看到是否很容易进入“下一个”表。这些可能是有益的。 (我注意到你已经有(id)。)

af:  INDEX(applicantid, filetype)  -- in either order

请运行EXPLAIN SELECT以查看优化程序选择的顺序,以及它为每个后续表格选择的索引。

char(36)闻起来像UUID或GUID。你做出CHARACTER SET latin1而不是utf8是件好事。但由于随机性,这些字段对索引很糟糕。见my blog。如果可能,请切换到MEDIUMINT UNSIGNED AUTO_INCREMENT,尽管它会涉及大量代码和架构更改。

filetype的两个实例是多余的?也就是说,您是否需要检查两个表的文件类型?这是一项额外的工作。

为了帮助初始查询,我们需要理解filetype的值的分布。 12更常见(或更少)吗? 1(或2)的行是否会在表格的开头(或结尾)附近聚集?

桌子有多大?如果innodb_buffer_pool_size有什么价值?你有多少RAM?

以下部分或全部可能密谋让您表现不佳:

  • UUID的随机性。
  • 大于的表将适合buffer_pool。
  • RAM不足以使buffer_pool变大。
  • 缓冲区是如此之大(相对于RAM),正在发生交换。

如果这些评论未能提供足够的速度,我建议重新构建查询以延迟从f获取:

SELECT f..., x...
    FROM (
        SELECT ... FROM applicants AS a
                   JOIN applicant_files AS af ON ...
                 WHERE ...
         ) AS x
    JOIN files AS f  ON x.fileid = f.id
    WHERE f.filetype = 1

警告:“此处显示的架构是缩小版本。” - 由于您的缩小,我推荐的可能不够!