Like and '=' equals to operator performance in sql - mysql 集群
Like and '=' equals to operator performance in sql - mysql cluster
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
运算符(第二次查询)而不是 =
(第一次查询)时查询执行速度更快?
你有两个完全不同的执行计划。
对于您的第一个查询,MySQL 通过 radcheck.attribute
上的索引查找具有 radcheck.attribute='Expiration'
的所有条目(并假设有 9 行适合)。然后它将使用 username
(以及 username
上的索引)为每个可能的用户名加入其他 tables,然后从 [=73] 中读取 userinfo.mobilephone
的值=] 看看是否合适。
对于您的第二个查询,它将检查 userinfo.mobilephone
上的索引以查找以 9876543210
开头的任何内容(假设它将找到 585 行)。然后它将使用 username
(以及 username
上的索引)加入其他 tables 以获取所有具有正确手机的用户名,然后从中读取 radcheck.attribute
的值table 看看是否合适。
当您实际上只有少数行以您的手机号码开头,但很多行以 radcheck.attribute='Expiration'
开头时,您的第二个查询当然会快得多,因为它必须执行执行的其余部分(加入,特别是从 table 中读取)的行数要少得多(尽管你的行数必须比解释中显示的多得多才能证明 8 秒是合理的)。
MySQL 必须猜测哪种方式更快,并根据您的查询和有关您的 table 的一些统计数据选择一种方式。它选择了完全错误的。
在您的第一个查询中,MySQL 假设在索引中查找 =
比在索引中查找 like
更好(并且只会产生 9 行,这是显然不正确)。在你的第二个查询中,它只需要选择在第一个或第二个索引中查找 like
更好 - 并且猜对了。但是如果你愿意,例如已经寻找 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
列替换为(整数)id
,并使其(的第一部分)成为主键,您可能会获得一些额外的性能你的三个 tables,并使用这个 id
加入你的 tables,因为你的索引会变得更小,你例如找到 Expiration
的值后就不必查找用户名了。这应该会减少您第一次查询的执行时间(基于对您的 table 和数据的一些假设,我猜想会减少 50% 以上——但这只是一个猜测,也可能只有 5%) .但由于它并不能证明为此更改整个应用程序(尽管其他查询也会从中受益),所以当您无论如何都必须对代码进行一些大的更改时,您可能会考虑它。 (您可以模拟它的某些部分并尝试通过添加索引 radcheck(attribute, username)
是否值得付出努力,这应该为您的第一个查询提供 30%-50% 的收益——假设 username
不是您的主键已经)
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
运算符(第二次查询)而不是 =
(第一次查询)时查询执行速度更快?
你有两个完全不同的执行计划。
对于您的第一个查询,MySQL 通过 radcheck.attribute
上的索引查找具有 radcheck.attribute='Expiration'
的所有条目(并假设有 9 行适合)。然后它将使用 username
(以及 username
上的索引)为每个可能的用户名加入其他 tables,然后从 [=73] 中读取 userinfo.mobilephone
的值=] 看看是否合适。
对于您的第二个查询,它将检查 userinfo.mobilephone
上的索引以查找以 9876543210
开头的任何内容(假设它将找到 585 行)。然后它将使用 username
(以及 username
上的索引)加入其他 tables 以获取所有具有正确手机的用户名,然后从中读取 radcheck.attribute
的值table 看看是否合适。
当您实际上只有少数行以您的手机号码开头,但很多行以 radcheck.attribute='Expiration'
开头时,您的第二个查询当然会快得多,因为它必须执行执行的其余部分(加入,特别是从 table 中读取)的行数要少得多(尽管你的行数必须比解释中显示的多得多才能证明 8 秒是合理的)。
MySQL 必须猜测哪种方式更快,并根据您的查询和有关您的 table 的一些统计数据选择一种方式。它选择了完全错误的。
在您的第一个查询中,MySQL 假设在索引中查找 =
比在索引中查找 like
更好(并且只会产生 9 行,这是显然不正确)。在你的第二个查询中,它只需要选择在第一个或第二个索引中查找 like
更好 - 并且猜对了。但是如果你愿意,例如已经寻找 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
列替换为(整数)id
,并使其(的第一部分)成为主键,您可能会获得一些额外的性能你的三个 tables,并使用这个 id
加入你的 tables,因为你的索引会变得更小,你例如找到 Expiration
的值后就不必查找用户名了。这应该会减少您第一次查询的执行时间(基于对您的 table 和数据的一些假设,我猜想会减少 50% 以上——但这只是一个猜测,也可能只有 5%) .但由于它并不能证明为此更改整个应用程序(尽管其他查询也会从中受益),所以当您无论如何都必须对代码进行一些大的更改时,您可能会考虑它。 (您可以模拟它的某些部分并尝试通过添加索引 radcheck(attribute, username)
是否值得付出努力,这应该为您的第一个查询提供 30%-50% 的收益——假设 username
不是您的主键已经)