3

MySQL-Cluster-gpl-7.4.12-1.el6.x86_64

查询 1

    select
    userinfo.username,
    userinfo.firstname,
    userinfo.lastname,
    userinfo.email,
    radcheck.attribute,
    radcheck.`value`,
    radusergroup.groupname,
    userinfo.id,
    userinfo.account_type,
    userinfo.workphone,
    userinfo.homephone,
    userinfo.mobilephone,
    userinfo.address,
    userinfo.zone,
    userinfo.account_state,
    userinfo.link_type,
    userinfo.device_owner,
    userinfo.email
    FROM
    userinfo
    INNER JOIN radcheck ON userinfo.username = radcheck.username
    INNER JOIN radusergroup ON userinfo.username = radusergroup.username
    WHERE
    radcheck.attribute='Expiration'  AND userinfo.mobilephone LIKE '9876543210%'

此查询大约需要8 秒才能完成以下是explain查询的输出

    +----+-------------+--------------+------+----------------------+-----------+---------+----------------------------+------+-------------+
    | id | select_type | table        | type | possible_keys        | key       | key_len | ref                        | rows | Extra       |
    +----+-------------+--------------+------+----------------------+-----------+---------+----------------------------+------+-------------+
    |  1 | SIMPLE      | radcheck     | ref  | username,attribute   | attribute | 34      | const                      |    9 | Using where |
    |  1 | SIMPLE      | userinfo     | ref  | username,mobilephone | username  | 131     | ctradius.radcheck.username |   13 | Using where |
    |  1 | SIMPLE      | radusergroup | ref  | username             | username  | 66      | ctradius.userinfo.username |   10 | Using where |
    +----+-------------+--------------+------+----------------------+-----------+---------+----------------------------+------+-------------+

查询 2

以下是在< 0.005s内完成的查询

    select
    userinfo.username,
    userinfo.firstname,
    userinfo.lastname,
    userinfo.email,
    radcheck.attribute,
    radcheck.`value`,
    radusergroup.groupname,
    userinfo.id,
    userinfo.account_type,
    userinfo.workphone,
    userinfo.homephone,
    userinfo.mobilephone,
    userinfo.address,
    userinfo.zone,
    userinfo.account_state,
    userinfo.link_type,
    userinfo.device_owner,
    userinfo.email
    FROM
    userinfo
    INNER JOIN radcheck ON userinfo.username = radcheck.username
    INNER JOIN radusergroup ON userinfo.username = radusergroup.username
    WHERE
    radcheck.attribute like 'Expiration%'  AND userinfo.mobilephone LIKE '9876543210%'

以下是查询的解释

 +----+-------------+--------------+-------+----------------------+-------------+---------+----------------------------+------+------------------------+
    | id | select_type | table        | type  | possible_keys        | key         | key_len | ref                        | rows | Extra                  |
    +----+-------------+--------------+-------+----------------------+-------------+---------+----------------------------+------+------------------------+
    |  1 | SIMPLE      | userinfo     | range | username,mobilephone | mobilephone | 203     | NULL                       |  585 | Using where; Using MRR |
    |  1 | SIMPLE      | radusergroup | ref   | username             | username    | 66      | ctradius.userinfo.username |   10 | Using where            |
    |  1 | SIMPLE      | radcheck     | ref   | username,attribute   | username    | 66      | ctradius.userinfo.username |   17 | Using where            |
    +----+-------------+--------------+-------+----------------------+-------------+---------+----------------------------+------+------------------------+

问题

为什么like使用运算符(第二个查询)而不是=(第一个查询)时查询执行速度更快?

4

1 回答 1

2

你有两个完全不同的执行计划。

radcheck.attribute='Expiration'对于您的第一个查询,MySQL通过索引查找所有条目radcheck.attribute(并假设有 9 行适合)。然后,它将使用每个 pssible 用户名的username(和上的索引username)连接其他表,然后从表中读取值userinfo.mobilephone并查看它是否适合。

对于您的第二个查询,它将检查索引以userinfo.mobilephone查找以开头的任何内容9876543210(假设它将找到 585 行)。然后,它将使用正确手机的所有用户名username(以及 上的索引)加入其他表,然后从表中读取 的值并查看它是否适合。usernameradcheck.attribute

当您实际上只有少数以您的手机号码开头的行,但很多行radcheck.attribute='Expiration'以从表中)的行数要少得多(尽管您必须比解释中显示的行数多得多才能证明 8 秒是合理的)。

MySQL 必须猜测哪种方式更快,并根据您的查询和有关表的一些统计数据选择一种方式。它选择了完全错误的。

在您的第一个查询中,MySQL 假设在索引中查找比=在索引中查找更好like(并且只会产生 9 行,这显然是不正确的)。like在您的第二个查询中,它只需要选择在第一个或第二个索引中查找是否更好 - 并且猜对了。但是,如果您例如寻找 a userinfo.mobilephone LIKE '0%',它可能会更慢(取决于您的数据)。

您可以尝试一些事情:

  • 使用optimize table userinfo, radcheck, radusergroup. 这将重新创建您的索引和统计信息。根据您的数据,它可能不再假设您的第一个查询只有 9 行。
  • 强制mysql始终按照您的第二个查询的顺序加入STRAIGHT_JOIN

    ...
    from radcheck
    straight_join userinfo ON userinfo.username = radcheck.username
    straight_join radusergroup ON userinfo.username = radusergroup.username
    where ...
    

    但这可能会导致在您正在寻找的情况下查询变慢,例如userinfo.mobilephone LIKE '0%',或者根本没有条件userinfo.mobilephone,所以在不同的情况下进行测试。

  • 继续使用like 'Expiration%',你甚至可以=在没有或只有短手机要寻找的情况下切换到它,但不能保证总是使用第二种方式(并且可能会随着其他 mysql 版本而改变)。

顺便说一句,如果您将username-columns 替换为 (integer) id,将其(第一部分)作为三个表的主键,并使用它id来连接您的表,您可能会获得一些额外的性能,因为您的索引会变得更小,并且您例如在找到值后不必查找用户名Expiration. 这应该会减少您的第一个查询的执行时间(基于对您的表和数据的一些假设,我猜想超过 50% - 但这只是一个猜测,也可能只有 5%)。但是,由于它不能证明为此更改整个应用程序是合理的(尽管其他查询也会从中受益),所以当您必须对代码进行一些大的更改时,您可能会考虑它。(您可以模拟其中的某些部分,并尝试通过添加索引是否值得付出努力radcheck(attribute, username),这应该为您的第一个查询提供 30%-50% - 假设username不是您的主键)

于 2016-10-03T10:49:21.413 回答