即使使用索引,MySQL语句也非常慢

时间:2013-04-10 14:26:42

标签: mysql sql

以下查询大约需要200秒才能完成。我想要实现的目标是获得6笔或更多付款的用户,但尚未订购任何订单(不同市场有2个订单表)。

u.idju.id都是主键。

我已将user_idorder_status合并为两个订单表中的一个索引。如果我删除了COUNT()表上的连接和mp_orders,则查询需要8秒才能完成,但是使用它会花费太长时间。我想我已经将我可以拥有的所有东西编入索引,但我不明白为什么需要这么长时间才能完成。有什么想法吗?

SELECT 
    u.id, 
    ju.name,
    COUNT(p.id) as payment_count, 
    COUNT(o.id) as order_count,
    COUNT(mi.id) as marketplace_order_count
FROM users as u
    INNER JOIN users2 as ju
        ON u.id = ju.id
    INNER JOIN payments as p
        ON u.id = p.user_id
    LEFT OUTER JOIN orders as o
        ON u.id = o.user_id
            AND o.order_status = 1
    LEFT OUTER JOIN mp_orders as mi
        ON u.id = mi.producer
            AND mi.order_status = 1
WHERE u.package != 1
AND u.enabled = 1
AND u.chart_ban = 0
GROUP BY u.id
HAVING COUNT(p.id) >= 6
    AND COUNT(o.id) = 0
    AND COUNT(mi.id) = 0
LIMIT 10

付款表

+-----------------+---------------+------+-----+---------+----------------+
| Field           | Type          | Null | Key | Default | Extra          |
+-----------------+---------------+------+-----+---------+----------------+
| id              | bigint(255)   | NO   | PRI | NULL    | auto_increment |
| user_id         | bigint(255)   | NO   |     | NULL    |                |
+-----------------+---------------+------+-----+---------+----------------+

订单表(mp_orders表几乎相同)

+-----------------+---------------+------+-----+---------+----------------+
| Field           | Type          | Null | Key | Default | Extra          |
+-----------------+---------------+------+-----+---------+----------------+
| id              | int(255)      | NO   | PRI | NULL    | auto_increment |
| order_number    | varchar(1024) | NO   | MUL | NULL    |                |
| user_id         | int(255)      | NO   | MUL | NULL    |                |
+-----------------+---------------+------+-----+---------+----------------+

2 个答案:

答案 0 :(得分:4)

您不需要对订单行进行COUNT操作,您需要检索没有订单的用户,这不是真的相同。

而不是计算,过滤没有订单的用户:

SELECT 
    u.id, 
    ju.name,
    COUNT(p.id) as payment_count
FROM users as u
    INNER JOIN users2 as ju
        ON u.id = ju.id
    INNER JOIN payments as p
        ON u.id = p.user_id
    LEFT OUTER JOIN orders as o
        ON u.id = o.user_id
            AND o.order_status = 1
    LEFT OUTER JOIN mp_orders as mi
        ON u.id = mi.producer
            AND mi.order_status = 1
WHERE u.package != 1
AND u.enabled = 1
AND u.chart_ban = 0
AND o.id IS NULL    -- filter happens here
AND mi.id IS NULL   -- and here
GROUP BY u.id
HAVING COUNT(p.id) >= 6
LIMIT 10

这会阻止引擎计算每个用户的每个订单,您将获得大量时间。

可以认为引擎应该使用索引进行计数,因此计数必须足够快 I will quote from a different site: InnoDB COUNT(id) - Why so slow?

  

可能与缓冲有关,InnoDb不会缓存索引   将实际数据行缓存到内存中,因为这样做   似乎是一个简单的扫描它不加载主键索引但是   将所有数据放入RAM,然后在其上运行查询。这可能需要   一段时间工作 - 希望如果您在此之后运行查询   在同一张桌子上,它们会跑得更快。

     

MyIsam将索引加载到RAM中,然后运行其计算   这个空间然后返回一个结果,因为索引一般很多   比表中的所有数据小得多,你应该看到一个   那里的直接差异。

     

另一种选择可能是innodb将数据存储在磁盘上的方式    - innodb文件是一个虚拟表空间,因此如果你有一个,你不一定按表中的数据排序   碎片化的数据文件然后这可能会为您创建问题   磁盘IO因此运行速度较慢。 MyIsam一般都是   顺序文件,因此如果您使用索引来访问数据   系统确切知道该行位于磁盘上的哪个位置 -   你对innodb没有这种奢侈,但我不这么认为   只需一个简单的计数即可发挥特殊问题(*)   ==================== http://dev.mysql.com/doc/refman/5.0/en/innodb-restrictions.html   解释了这个:

     

InnoDB不保留表中的内部行数。 (在   实践,由于多版本化,这会有点复杂。)   要处理SELECT COUNT(*)FROM t语句,InnoDB必须扫描一个   表的索引,如果索引不完全,则需要一些时间   在缓冲池中。要快速计数,您必须使用计数器   您自己创建的表,并让您的应用程序更新它   它插入和删除它。如果你的表没有改变   通常,使用MySQL查询缓存是一个很好的解决方案。显示表   如果近似行数足够,也可以使用STATUS。看到   第14.2.11节“InnoDB性能调优技巧”。   =================== todd_farmer:它确实解释了差异--MyISAM理解COUNT(ID)其中ID是PK列   与COUNT(*)相同,MyISAM在InnoDB时保持预先计算   没有。

答案 1 :(得分:3)

请尝试通过COUNT() = 0检查删除IS NULL

SELECT 
    u.id, 
    ju.name,
    COUNT(p.id) as payment_count, 
    0 as order_count,
    0 as marketplace_order_count
FROM users as u
    INNER JOIN users2 as ju
        ON u.id = ju.id
    INNER JOIN payments as p
        ON u.id = p.user_id
    LEFT OUTER JOIN orders as o
        ON u.id = o.user_id
       AND o.order_status = 1
    LEFT OUTER JOIN mp_orders as mi
        ON u.id = mi.producer
       AND mi.order_status = 1
WHERE 
    u.package != 1
AND u.enabled = 1
AND u.chart_ban = 0
AND mi.id IS NULL
AND o.id IS NULL
GROUP BY u.id
HAVING COUNT(p.id) >= 6
LIMIT 10

但我认为8秒对普通查询来说仍然太多了。您应该发布主查询的解释计划而不使用OUTER JOINS来查看错误,例如包,启用和图表禁止过滤器可能完全破坏它。