如何在使用count和group by时加快查询速度

时间:2016-04-13 20:04:44

标签: mysql performance group-by percona filesort

我有两张名为卖家商品的牌桌。它们通过第三个表( seller_item )使用“n”到“m”外键关系连接。

现在我尝试回答这个要求:“作为卖家,我想要一份我的竞争对手名单,其中包括我销售的商品数量以及他们的销售情况”。 因此,列出了与一个特定卖家相关的重叠项目数量的所有卖家。 此外,我希望按计数和有限排序。 但是查询使用临时表和文件输出非常慢。 解释说:

使用where;使用索引;使用临时;使用filesort

如何加快速度?

以下是查询:

SELECT
          COUNT(*) AS itemCount,
          s.sellerName
        FROM
          seller s,
          seller_item si
        WHERE
          si.itemId IN 
           (SELECT itemId FROM seller_item WHERE sellerId = 4711)
        AND
          si.sellerId=s.id
        GROUP BY
          sellerName
        ORDER BY
          itemCount DESC
        LIMIT 50;

表格defs:

CREATE TABLE `seller` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`sellerName` varchar(50) NOT NULL
PRIMARY KEY (`id`),
UNIQUE KEY `unique_index` (`sellerName`),
) ENGINE=InnoDB 

contains about 200.000 rows

-

CREATE TABLE `item` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`itemName` varchar(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_index` (`itemName`),
) ENGINE=InnoDB

contains about 100.000.000 rows

-

CREATE TABLE `seller_item` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`sellerId` bigint(20) unsigned NOT NULL,
`itemId` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `sellerId` (`sellerId`,`itemId`),
KEY `item_id` (`itemId`),
CONSTRAINT `fk_1` FOREIGN KEY (`sellerId`) REFERENCES `seller` (`id`)            ON DELETE CASCADE ON UPDATE NO ACTION,
CONSTRAINT `fk_2` FOREIGN KEY (`itemId`) REFERENCES `item` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB

contains about 170.000.000 rows

数据库是Mysql Percona 5.6

EXPLAIN的输出:

+----+-------------+-------------+--------+----------------------+-----    ---------+---------+---------------------+------+----------------------------------------------+
| id | select_type | table       | type   | possible_keys        | key                | key_len | ref                 | rows | Extra                                           |
+----+-------------+-------------+--------+----------------------+--------------+---------+---------------------+------+----------------------------------------------+
|  1 | SIMPLE      | s           | index  | PRIMARY,unique_index | unique_index | 152     | NULL                |    1 | Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | si          | ref    | sellerId,item_id     | sellerId     | 8       | tmp.s.id            |    1 | Using index                                  |
|  1 | SIMPLE      | seller_item | eq_ref | sellerId,item_id     |  sellerId     | 16      | const,tmp.si.itemId |    1 | Using where; Using  index                     |
+----+-------------+-------------+--------+----------------------+--------------+---------+---------------------+------+----------------------------------------------+

4 个答案:

答案 0 :(得分:3)

我怀疑在大小的数据库上实时快速运行查询是可行的,特别是对于库存中有大量热门商品的卖家。

你应该实现它。创建一个这样的表

CREATE TABLE
        matches
        (
        seller INT NOT NULL,
        competitor INT NOT NULL,
        matches INT NOT NULL,
        PRIMARY KEY
                (seller, competitor)
        )

并在cron脚本中批量更新:

DELETE
FROM    matches
WHERE   seller = :seller

INSERT
INTO    matches (seller, competitor, matches)
SELECT  si.seller, sc.seller, COUNT(*) cnt
FROM    seller_item si
JOIN    seller_item sc
ON      sc.item = si.item
        AND sc.seller <> si.seller
WHERE   si.seller = :seller
GROUP BY
        si.seller, sc.seller
ORDER BY
        cnt DESC
LIMIT   50

您还需要在(seller, item)PRIMARY KEY seller_item。现在的方式是,按项目查找卖家需要两次查询而不是一次:首先使用KEY (item)逐项查找,然后使用PRIMARY KEY (id)

逐个卖家

答案 1 :(得分:2)

<强>再形成

我认为这是你真正想要的:

SELECT  si2.sellerId, COUNT(DISTINCT si2.itemId) AS itemCount
    FROM  seller_item si1
    JOIN  seller_item si2 ON si2.itemId = si1.itemId
    WHERE  si1.sellerId = 4711
    GROUP BY  si2.sellerId
    ORDER BY  itemCount DESC
    LIMIT  50;

(注意:DISTINCT可能是不必要的。)

用语言:对于卖家#4711,找到他卖的商品,然后找出哪些卖家卖的几乎相同的商品。 (我没有尝试从结果集中过滤掉#4711。)

效率更高N:M

但仍然存在效率低下的问题。让我们剖析你的多对多映射表(seller_item)。

  • 它有id,可能不会用于任何事情。摆脱它。
  • 然后将UNIQUE(sellerId, itemId)提升为PRIMARY KEY(sellerId, itemId)
  • 现在将INDEX(itemId)更改为INDEX(itemId, sellerId),以便查询的最后一个阶段可以&#34;使用索引&#34;。

Blog discussing that further

你有一个非常大的数据集;你已经调试了你的应用程序。考虑删除FOREIGN KEYs;它们有点贵。

获取卖家名称

可以 使JOINsellers获得sellerName。但首先尝试使用sellerId。然后添加名称。验证计数不会膨胀(经常发生)并且查询不会减慢。

如果出现任何问题,请执行

SELECT s.sellerName, x.itemCount
    FROM ( .. the above query .. ) AS x
    JOIN sellers AS s  USING(sellerId);

(您可以选择添加ORDER BY sellerName。)

答案 2 :(得分:0)

我不确定这对您的数据库有多快,但我会像这样编写查询。

    select * from (
        select seller.sellerName,
        count(otherSellersItems.itemId) itemCount from (
            select sellerId, itemId from seller_item where sellerId != 4711
        ) otherSellersItems
        inner join ( 
            select itemId from seller_item where sellerId = 4711
        ) thisSellersItems
        on otherSellersItems.itemId = thisSellersItems.itemId
        inner join seller
        on otherSellersItems.sellerId = seller.id
        group by seller.sellerName
    ) itemsSoldByOtherSellers
    order by itemCount desc
    limit 50 ;

答案 3 :(得分:0)

由于我们将(可能很大的)结果集限制为最多50行,因此我会推迟获取卖家名称,直到我们收到计数后,我们才需要获得50个卖家名称。

首先,我们通过seller_id获取itemcount

SELECT so.seller_id
     , COUNT(*) AS itemcount
  FROM seller_item si
  JOIN seller_item so
    ON so.item_id = si.item_id
 WHERE si.seller_id = 4711
 GROUP BY so.seller_id
 ORDER BY COUNT(*) DESC, so.seller_id DESC
 LIMIT 50

为了提高性能,我会为so的联接提供合适的覆盖索引。 e.g。

CREATE UNIQUE INDEX seller_item_UX2 ON seller_item(item_id,seller_id) 

通过使用&#34;覆盖索引&#34;,MySQL可以完全从索引页面满足查询,而无需访问基础表中的页面。

创建新索引后,我会删除singleton item_id列上的索引,因为该索引现在是多余的。 (任何可以有效使用该索引的查询都能够有效地使用以item_id作为前导列的复合索引。)

没有绕过&#34;使用filesort&#34;操作。 MySQL必须先评估每行上的COUNT()聚合,然后才能执行排序。对于MySQL,没有办法(给定当前模式)使用索引按顺序返回行以避免排序操作。

一旦我们拥有(最多)五十行的那一组,那么我们就可以获得卖家名称。

要获取卖家名称,我们可以在SELECT列表中使用相关子查询,也可以使用连接操作。

1)在SELECT列表中使用相关子查询,例如

SELECT so.seller_id
     , ( SELECT s.sellername
           FROM seller s
          WHERE s.seller_id = so.seller_id
          ORDER BY s.seller_id, s.sellername
          LIMIT 1
       ) AS sellername   
     , COUNT(*) AS itemcount
  FROM seller_item si
  JOIN seller_item so
    ON so.item_id = si.item_id
 WHERE si.seller_id = 4711
 GROUP BY so.seller_id
 ORDER BY COUNT(*) DESC, so.seller_id DESC
 LIMIT 50

(我们知道子查询将被执行(最多)五十次,对于外部查询返回的每一行执行一次。五十次执行(使用合适的索引)并不坏,至少与50,000相比执行。)

或者,2)使用连接操作,例如

SELECT c.seller_id
     , s.sellername
     , c.itemcount
  FROM ( 
         SELECT so.seller_id
              , COUNT(*) AS itemcount
           FROM seller_item si
           JOIN seller_item so
             ON so.item_id = si.item_id
          WHERE si.seller_id = 4711
          GROUP BY so.seller_id
          ORDER BY COUNT(*) DESC, so.seller_id DESC
          LIMIT 50
       ) c
  JOIN seller s
    ON s.seller_id = c.seller_id
 ORDER BY c.itemcount DESC, c.seller_id DESC    

(同样,我们知道内联视图c将返回(最多)五十行,并且获得五十个卖家名(使用合适的索引)应该很快。

汇总表

如果我们对实现进行非规范化,并添加了包含item_id(作为主键)和&#34; count&#34;的摘要表。对于该item_id的卖家数量,我们的查询可以利用这一点。

举例说明:

CREATE TABLE item_seller_count
( item_id BIGINT NOT NULL PRIMARY KEY
, seller_count BIGINT NOT NULL
) Engine=InnoDB
;

INSERT INTO item_seller_count (item_id, seller_count)
SELECT d.item_id
     , COUNT(*)
  FROM seller_item d
 GROUP BY d.item_id
 ORDER BY d.item_id
;

CREATE UNIQUE INDEX item_seller_count_IX1 
  ON item_seller_count (seller_count, item_id)
;

新的摘要表将变为&#34;不同步&#34;从seller_item表中插入/更新/删除行时。

填充此表将占用资源。但是,如果有这个可以加快我们正在进行的类型的查询。