【问题标题】:MySQL stops using index when additional constraints are added当添加额外的约束时 MySQL 停止使用索引
【发布时间】:2014-08-03 09:22:15
【问题描述】:

使用EXPLAIN 表明以下查询不使用我的索引,有人可以解释一下是怎么回事吗?

    SELECT  u.id AS userId, firstName, profilePhotoId, preferredActivityId, preferredSubActivityId, availabilityType,
         3959 * ACOS(COS(radians(requestingUserLat)) * COS(radians(u.latitude)) * COS(radians(u.longitude) - radians(requestingUserLon)) + SIN(radians(requestingUserLat)) * SIN(radians(u.latitude))) AS distanceInMiles
    FROM users u
   WHERE u.latitude     between lat1    and lat2 -- MySQL 5.7 supports Point data type, but it is not indexed in innoDB. I store latitude and longitude as DOUBLE for now
     AND u.longitude    between lon1    and lon2
     AND u.dateOfBirth  between maxAge  and minAge -- dates are in millis, therefore maxAge will have a smaller value than minAge and so it needs to go first
     AND IF(gender       is null, TRUE, u.gender = gender)
     AND IF(activityType is null, TRUE, u.preferredActivityType = activityType)
     AND u.accountState = 'A'
     AND u.id != userId
  HAVING distanceInMiles < searchRadius ORDER BY distanceInMiles LIMIT pagingStart, pagingLength;


CREATE INDEX `findMatches` ON `users` (`latitude` ASC, `longitude` ASC, `dateOfBirth` ASC) USING BTREE;


在这个阶段根本不使用索引。为了让它工作,我需要从SELECT 语句中注释掉一堆列,并从WHERE 子句中删除所有未索引的列。以下作品:

    SELECT  u.id AS userId --, firstName, profilePhotoId, preferredActivityId, preferredSubActivityId, availabilityType,
         3959 * ACOS(COS(radians(requestingUserLat)) * COS(radians(u.latitude)) * COS(radians(u.longitude) - radians(requestingUserLon)) + SIN(radians(requestingUserLat)) * SIN(radians(u.latitude))) AS distanceInMiles
    FROM users u
   WHERE u.latitude     between lat1    and lat2 -- MySQL 5.7 supports Point data type, but it is not indexed in innoDB. We store latitude and longitude as DOUBLE for now
     AND u.longitude    between lon1    and lon2
     AND u.dateOfBirth  between maxAge  and minAge -- dates are in millis, therefore maxAge will have a smaller value than minAge and so it needs to go first
    -- AND IF(gender         is null, TRUE, u.gender = gender)
    -- AND IF(activityType is null, TRUE, u.preferredActivityType = activityType)
    -- AND u.accountState = 'A'
    -- AND u.id != userId
  HAVING distanceInMiles < searchRadius ORDER BY distanceInMiles LIMIT pagingStart, pagingLength;


我尝试过的其他方法:
除了包含所有 3 个键的多部分索引之外,我还尝试创建 3 个不同的单部分索引。根据文档here,优化器不应该通过创建符合条件的行的 UNION 来合并它们,从而进一步加快执行速度吗?它不这样做,它仍然选择多部分(覆盖)索引。


非常感谢任何帮助!

【问题讨论】:

    标签: mysql sql indexing query-optimization


    【解决方案1】:

    这有点难以解释。

    使用索引的查询正在使用它,因为索引是“覆盖”索引。也就是说,索引中的所有列都在查询中。唯一真正有效使用的索引部分是latitude 上的条件。

    通常,覆盖索引只有查询中提到的列。但是,主键用于引用记录,所以我猜测users.Id 是表上的主键。并且正在扫描索引以查找latitude 的有效值。

    没有使用索引的查询没有使用它有两个原因。首先,列上的条件是不等式。索引查找只能使用相等条件和一个不等式。这意味着该索引只能以最有效的方法用于latitude。其次,查询中的附加列无论如何都需要转到数据页。

    换句话说,优化器实际上是在说:“为什么要费心去索引扫描索引然后扫描数据页?相反,我可以只扫描数据页并一次获取所有内容。”

    毫无疑问,您的下一个问题是:“但是如何让我的查询更快?”我的建议是调查spatial indexes

    【讨论】:

    • 谢谢戈登。从 MySQL v5.7 开始,InnoDB supports spatial data types, but not spatial indexes. 让我们忘记这些列包含空间信息,让我们将它们视为DOUBLE。运行EXPLAIN,我可以看到注释掉条件列dateOfBirth 和下一步longitude,确实会逐渐减少使用的报告索引键长度。我认为这意味着有效地使用了整个覆盖索引,而不仅仅是latitude。你怎么看?
    • @DTs 。 . .覆盖索引可能仍仅用于获取列。扫描索引可以比扫描表更快。
    • 还是一头雾水。我知道获取非索引列需要读取数据页,但是如果报告的索引键长度显示索引的所有 3 列都已使用,那么索引的 longitudedataOfBirth 部分如何未使用用于定位符合条件的行的相应 PK?我试图在文档中找到您提到的限制“索引搜索只能使用相等条件和一个不等式。”,您有任何链接吗?谢谢。
    • 也许这个链接会有所帮助。它是关于 SQL Server 的,但想法应该类似:blog.sqlauthority.com/2009/08/24/…
    • 那里没有问题,我指的是讨论在多部分索引中使用多个范围的限制的文档。无论如何,我最终决定采用不同的方法,这里讨论的一些问题仍然存在。 Here's the new approach.
    猜你喜欢
    • 1970-01-01
    • 2015-09-29
    • 2017-09-16
    • 1970-01-01
    • 2014-06-01
    • 2012-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多