简体   繁体   English

当子查询具有组列时,MySQL 8 不使用 INDEX

[英]MySQL 8 is not using INDEX when subquery has a group column

We have just moved from mariadb 5.5 to MySQL 8 and some of the update queries have suddenly become slow.我们刚刚从 mariadb 5.5 迁移到 MySQL 8,一些更新查询突然变慢了。 On more investigation, we found that MySQL 8 does not use index when the subquery has group column.经过进一步调查,我们发现当子查询具有组列时,MySQL 8 不使用索引。

For example, below is a sample database.例如,下面是一个示例数据库。 Table users maintain the current balance of the users per type and table 'accounts' maintain the total balance history per day.users维护每种类型的用户的当前余额,表“帐户”维护每天的总余额历史记录。

CREATE DATABASE 'test';

CREATE TABLE `users` (
  `uid` int(10) unsigned NOT NULL DEFAULT '0',
  `balance` int(10) unsigned NOT NULL DEFAULT '0',
  `type` int(10) unsigned NOT NULL DEFAULT '0',
  KEY (`uid`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `accounts` (
  `uid` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `balance` int(10) unsigned NOT NULL DEFAULT '0',
  `day` int(10) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`uid`),
  KEY `day` (`day`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Below is a explanation for the query to update accounts以下是对更新帐户的查询的说明

mysql> explain update accounts a inner join (
      select uid, sum(balance) balance, day(current_date()) day from users) r 
           on r.uid=a.uid and r.day=a.day set a.balance=r.balance;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                          |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
|  1 | UPDATE      | NULL  | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL |     NULL | no matching row in const table |
|  2 | DERIVED     | users | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | NULL                           |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
2 rows in set, 1 warning (0.00 sec)

As you can see, mysql is not using index.如您所见,mysql 没有使用索引。

On more investigation, I found that if I remove sum() from the subquery, it starts using index.在更多调查中,我发现如果从子查询中删除sum() ,它会开始使用 index.js 。 However, that's not the case with mariadb 5.5 which was correctly using the index in all the case.但是,mariadb 5.5 并非如此,它在所有情况下都正确使用了索引。

Below are two select queries with and without sum() .下面是两个带有和不带有sum()选择查询。 I've used select query to cross check with mariadb 5.5 since 5.5 does not have explanation for update queries.我已经使用select查询与 mariadb 5.5 进行交叉检查,因为 5.5 没有对更新查询的解释。

mysql> explain select * from accounts a inner join (
        select uid, balance, day(current_date()) day from users
         ) r on r.uid=a.uid and r.day=a.day ;
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------+
| id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref        | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------+
|  1 | SIMPLE      | a     | NULL       | ref    | PRIMARY,day   | day     | 4       | const      |    1 |   100.00 | NULL  |
|  1 | SIMPLE      | users | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | test.a.uid |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)

and with sum()sum()

mysql> explain select * from accounts a inner join (
         select uid, sum(balance) balance, day(current_date()) day from users
            ) r on r.uid=a.uid and r.day=a.day ;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                          |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
|  1 | PRIMARY     | NULL  | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL |     NULL | no matching row in const table |
|  2 | DERIVED     | users | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | NULL                           |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
2 rows in set, 1 warning (0.00 sec)

Below is output from mariadb 5.5以下是 mariadb 5.5 的输出

MariaDB [test]> explain select * from accounts a inner join (
       select uid, sum(balance) balance, day(current_date()) day from users
             ) r on r.uid=a.uid and r.day=a.day ;
+------+-------------+------------+------+---------------+------+---------+-----------------------+------+-------------+
| id   | select_type | table      | type | possible_keys | key  | key_len | ref                   | rows | Extra       |
+------+-------------+------------+------+---------------+------+---------+-----------------------+------+-------------+
|    1 | PRIMARY     | a          | ALL  | PRIMARY,day   | NULL | NULL    | NULL                  |    1 |             |
|    1 | PRIMARY     | <derived2> | ref  | key0          | key0 | 10      | test.a.uid,test.a.day |    2 | Using where |
|    2 | DERIVED     | users      | ALL  | NULL          | NULL | NULL    | NULL                  |    1 |             |
+------+-------------+------------+------+---------------+------+---------+-----------------------+------+-------------+
3 rows in set (0.00 sec)

Any idea what are we doing wrong?知道我们做错了什么吗?

As others have commented, break your update query apart...正如其他人评论的那样,将您的更新查询分开...

update accounts join更新帐户加入

then your query那么你的查询

on condition of the join.在加入的条件下。

Your inner select query of您的内部选择查询

select uid, sum(balance) balance, day(current_date()) day from users

is the only thing that is running, getting some ID and the sum of all balances and whatever the current day.是唯一正在运行的东西,获取一些 ID 和所有余额的总和以及当天的任何内容。 You never know which user is getting updated, let alone the correct amount.你永远不知道哪个用户正在更新,更不用说正确的数量了。 Start by getting your query to see your expected results per user ID.首先获取查询以查看每个用户 ID 的预期结果。 Although the context does not make sense that your users table has a "uid", but no primary key thus IMPLYING there is multiple records for the same "uid".虽然上下文没有意义,您的用户表具有“uid”,但没有主键,因此暗示同一个“uid”有多个记录。 The accounts (to me) implies ex: I am a bank representative and sign up multiple user accounts.帐户(对我而言)意味着例如:我是银行代表并注册多个用户帐户。 Thus my active portfolio of client balances on a given day is the sum from users table.因此,我在某一天的活跃客户余额组合是用户表中的总和。

Having said that, lets look at getting that answer话虽如此,让我们看看得到那个答案

select
      u.uid,
      sum( u.balance ) allUserBalance
   from
      users u
   group by
      u.uid

This will show you per user what their total balance is as of right now.这将向您显示每位用户目前的总余额。 The group by now gives you the "ID" key to tie back to the accounts table.该组现在为您提供与帐户表相关联的“ID”键。 In MySQL, the syntax of a correlated update for this scenario would be... (I am using above query and giving alias "PQ" for PreQuery for the join)在 MySQL 中,此场景的相关更新的语法将是...(我使用上述查询并为连接的 PreQuery 提供别名“PQ”)

update accounts a
   JOIN
   ( select
          u.uid,
          sum( u.balance ) allUserBalance
       from
          users u
       group by
          u.uid ) PQ
      -- NOW, the JOIN ON clause ties the Accounts ID to the SUM TOTALS per UID balance
      on a.uid = PQ.uid
   -- NOW you can SET the values
   set Balance = PQ.allUserBalance,
       Day = day( current_date())

Now, the above will not give a proper answer if you have accounts that no longer have user entries associated... such as all users get out.现在,如果您的帐户不再具有关联的用户条目……例如所有用户都退出,那么上面的内容将不会给出正确的答案。 So, whatever accounts have no users, their balance and day record will be as of some prior day.因此,任何没有用户的帐户,其余额和日期记录都将是前一天的。 To fix this, you could to a LEFT-JOIN such as要解决此问题,您可以使用 LEFT-JOIN,例如

update accounts a
   LEFT JOIN
   ( select
          u.uid,
          sum( u.balance ) allUserBalance
       from
          users u
       group by
          u.uid ) PQ
      -- NOW, the JOIN ON clause ties the Accounts ID to the SUM TOTALS per UID balance
      on a.uid = PQ.uid
   -- NOW you can SET the values
   set Balance = coalesce( PQ.allUserBalance, 0 ),
       Day = day( current_date())

With the left-join and COALESCE(), if there is no record summation in the user table, it will set the account balance to zero.使用 left-join 和 COALESCE(),如果用户表中没有记录总和,它会将帐户余额设置为零。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM