4

以下查询大约需要 200 秒才能完成。我想要实现的是让已经进行了 6 次或更多付款但尚未下任何订单的用户(有 2 个针对不同市场的订单表)。

u.id,ju.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    |                |
+-----------------+---------------+------+-----+---------+----------------+
4

2 回答 2

4

您不需要计算订单的行数,您需要检索没有订单的用户,这不是一回事。

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

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

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

可以认为引擎应该使用索引来进行计数,因此计数必须足够快。
我将引用另一个站点:InnoDB COUNT(id) - 为什么这么慢?

这可能与缓冲有关,InnoDb 不会将它缓存到内存中的索引缓存到实际数据行中,因为这似乎是一个简单的扫描,它不是将主键索引加载,而是将所有数据加载到 RAM 中,并且然后在其上运行您的查询。这可能需要一些时间来工作 - 希望如果您在同一张表上运行查询,那么它们会运行得更快。

MyIsam 将索引加载到 RAM 中,然后在此空间上运行其计算,然后返回结果,因为索引通常比表中的所有数据小得多,您应该在那里看到立即的差异。

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

InnoDB 不保留 table 中的内部行数。(实际上,由于多版本控制,这会有些复杂。)要处理 SELECT COUNT(*) FROM t 语句,InnoDB 必须扫描表的索引,如果索引不完全在缓冲区中,这需要一些时间水池。要获得快速计数,您必须使用您自己创建的计数器表,并让您的应用程序根据它所做的插入和删除来更新它。如果您的表不经常更改,使用 MySQL 查询缓存是一个很好的解决方案。如果近似的行数足够,也可以使用 SHOW TABLE STATUS。请参阅第 14.2.11 节,“InnoDB 性能调优技巧”。=================== todd_farmer:它实际上确实解释了差异 - MyISAM 理解 COUNT(ID) 其中 ID 是 PK 列与 COUNT(*) 相同,

于 2013-04-10T14:39:06.680 回答
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 的主查询的解释计划以查看问题所在,例如包、启用和图表禁止过滤器可能会完全破坏它。

于 2013-04-10T14:36:59.023 回答