mysql没有拿起最佳索引

时间:2018-05-24 11:47:56

标签: mysql query-optimization

这是我的表:

@media screen and (min-width: 1500px) and (max-width: 1600px){
    /*Your style */
}

对于查询1:

CREATE TABLE `idx_weight` ( `ID` bigint(20) NOT NULL AUTO_INCREMENT, `SECURITY_ID` bigint(20) NOT NULL COMMENT, `CONS_ID` bigint(20) NOT NULL, `EFF_DATE` date NOT NULL, `WEIGHT` decimal(9,6) DEFAULT NULL, PRIMARY KEY (`ID`), UNIQUE KEY `BPK_AK` (`SECURITY_ID`,`CONS_ID`,`EFF_DATE`), KEY `idx_weight_ix` (`SECURITY_ID`,`EFF_DATE`) ) ENGINE=InnoDB AUTO_INCREMENT=75334536 DEFAULT CHARSET=utf8

explain select SECURITY_ID, min(EFF_DATE) as startDate, max(EFF_DATE) as endDate from idx_weight where security_id = 1782

此查询运行正常。

现在查询2(唯一改变的是security_id参数):

+----+-------------+------------+------+----------------------+---------------+---------+-------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+------+----------------------+---------------+---------+-------+--------+-------------+ | 1 | SIMPLE | idx_weight | ref | BPK_AK,idx_weight_ix | idx_weight_ix | 8 | const | 887856 | Using index | +----+-------------+------------+------+----------------------+---------------+---------+-------+--------+-------------+

explain select SECURITY_ID, min(EFF_DATE) as startDate, max(EFF_DATE) as endDate from idx_weight where security_id = 26622

请注意,它会获取索引+----+-------------+------------+------+----------------------+--------+---------+-------+----------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+------+----------------------+--------+---------+-------+----------+-------------+ | 1 | SIMPLE | idx_weight | ref | BPK_AK,idx_weight_ix | BPK_AK | 8 | const | 10700002 | Using index | +----+-------------+------------+------+----------------------+--------+---------+-------+----------+-------------+ ,实际查询的运行时间超过1分钟。

这是不正确的。第二次花了10秒钟。我猜测第一次索引不在缓冲池中。

我可以通过附加BPK_AK

来获得解决方法

group by security_id

explain select SECURITY_ID, min(EFF_DATE) as startDate, max(EFF_DATE) as endDate from idx_weight where security_id = 26622 group by security_id

但我仍然不明白为什么mysql不会为某些+----+-------------+------------+-------+----------------------+---------------+---------+------+-------+---------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+-------+----------------------+---------------+---------+------+-------+---------------------------------------+ | 1 | SIMPLE | idx_weight | range | BPK_AK,idx_weight_ix | idx_weight_ix | 8 | NULL | 10314 | Using where; Using index for group-by | +----+-------------+------------+-------+----------------------+---------------+---------+------+-------+---------------------------------------+ 选择idx_weight_ix,这是此查询的覆盖索引(并且便宜得多)。有什么想法吗?

=============================================== ==========================

更新: @oysteing 学到了一个新技巧,很酷! :)

这是优化程序跟踪:

查询1:https://gist.github.com/aping/c4388d49d666c43172a856d77001f4ce

查询2:https://gist.github.com/aping/1af5504b428ca136a8b1c41c40d763e4

一些可能有用的额外信息:

来自security_id

INFORMATION_SCHEMA.STATISTICS
<{1}} +------------+---------------+--------------+-------------+-------------+ | NON_UNIQUE | INDEX_NAME | SEQ_IN_INDEX | COLUMN_NAME | CARDINALITY | +------------+---------------+--------------+-------------+-------------+ | 0 | BPK_AK | 1 | SECURITY_ID | 74134 | | 0 | BPK_AK | 2 | CONS_ID | 638381 | | 0 | BPK_AK | 3 | EFF_DATE | 68945218 | | 1 | idx_weight_ix | 1 | SECURITY_ID | 61393 | | 1 | idx_weight_ix | 2 | EFF_DATE | 238564 | +------------+---------------+--------------+-------------+-------------+ CARDINALITY不同,但从技术上讲它们应该完全相同,我是对的吗?

从此:https://dba.stackexchange.com/questions/49656/find-the-size-of-each-index-in-a-mysql-table?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa

SECURITY_ID

索引大小约为800MB,而不是1.3GB。

正在运行+---------------+-------------------+ | index_name | indexentry_length | +---------------+-------------------+ | BPK_AK | 1376940279 | | idx_weight_ix | 797175951 | +---------------+-------------------+ 会返回select count(*) from idx_weight where security_id = 1782

509994返回select count(*) from idx_weight where security_id = 26622

然后强制使用5828054查询1:

BPK_AK花了0.2秒。

基本上,select SQL_NO_CACHE SECURITY_ID, min(EFF_DATE) as startDate, max(EFF_DATE) as endDate from idx_weight use index (BPK_AK) where security_id = 1782的行数比26622高10倍,但使用相同的索引时,花费的时间要多50倍。

PS:缓冲池大小为25GB。

3 个答案:

答案 0 :(得分:1)

当您混合普通列(SECURITY_ID)和聚合函数(在您的情况下为min&amp; max)时,您应该使用GROUP BY。如果你不这样做,MySQL可以免费提供任何结果。使用GROUP BY,您将获得正确的结果。较新的MySQL数据库默认强制执行此行为。

当您省略GROUP BY时未选择第二个索引的原因很可能是由于聚合函数不限于同一组(= security_id)abd,因此不能用作限制器。< / p>

答案 1 :(得分:1)

优化程序跟踪显示索引选择不同的原因是由于从InnoDB收到的估计值。对于每个潜在索引,优化程序会要求存储引擎估计该范围内的记录数。对于第一个查询,它得到以下估计值:

BPK_AK:       1031808
idx_weight_ix: 887856

因此,idx_weight_ix的估计读取成本最低,并且选择此索引。对于第二个查询,估计值为:

BPK_AK:        11092112
idx_weight_ix: 12003098

由于行数较少,BPK_AK的估计读取成本最低。你可以说MySQL应该知道两种情况下范围内的实际行数是相同的,但是这个逻辑还没有实现。

我不知道InnoDB如何计算这个估计的细节,但它基本上做两个“索引潜水”来找到范围中的第一行和最后一行,然后以某种方式计算两者之间的“距离”。可能是索引页中的未使用空间会影响估计值,并且OPTIMIZE TABLE可以解决这个问题,但是在这么大的表上运行OPTIMIZE TABLE可能需要很长时间。

解决此问题的最快方法是添加一个GROUP BY子句,如其他几个人所提到的那样。然后MySQL只需要读取每组2行;自EFF_DATE为security_id的每个值排序的第一个和最后一个索引。或者,您可以使用FORCE INDEX强制使用特定索引。

MySQL 8.0也可能会更好地处理这个问题。成本模型有所改变,它会在缓存池中缓存的“冷”索引上产生更高的成本。

答案 2 :(得分:0)

  

我可以通过security_id

附加组来获得解决方法

嗯,是的。我不会以任何其他方式执行此操作,因为当您使用聚合函数时,您需要按某种方式进行分组。我甚至不知道MySQL允许你解决它。

我认为@slaakso是对的。支持他。