我有两张名为卖家和商品的牌桌。它们通过第三个表( 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 |
+----+-------------+-------------+--------+----------------------+--------------+---------+---------------------+------+----------------------------------------------+
答案 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;。你有一个非常大的数据集;你已经调试了你的应用程序。考虑删除FOREIGN KEYs
;它们有点贵。
获取卖家名称
可以 使JOIN
到sellers
获得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表中插入/更新/删除行时。
填充此表将占用资源。但是,如果有这个可以加快我们正在进行的类型的查询。