簡體   English   中英

為什么添加INNER JOIN會使此查詢變得如此之慢?

[英]Why does adding an INNER JOIN make this query so slow?

我有一個包含以下三個表的數據庫:

匹配表有200,000場比賽......

CREATE TABLE `matches` (
`match_id` bigint(20) unsigned NOT NULL,
`start_time` int(10) unsigned NOT NULL,
PRIMARY KEY (`match_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

英雄表有~100英雄......

CREATE TABLE `heroes` (
`hero_id` smallint(5) unsigned NOT NULL,
`name` char(40) NOT NULL,
PRIMARY KEY (`hero_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

matches_heroes表有2,000,000個關系(每場比賽10個隨機英雄)......

CREATE TABLE `matches_heroes` (
`relation_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`match_id` bigint(20) unsigned NOT NULL,
`hero_id` smallint(6) unsigned NOT NULL,
PRIMARY KEY (`relation_id`),
KEY `match_id` (`match_id`),
KEY `hero_id` (`hero_id`),
CONSTRAINT `matches_heroes_ibfk_2` FOREIGN KEY (`hero_id`)
REFERENCES `heroes` (`hero_id`),
CONSTRAINT `matches_heroes_ibfk_1` FOREIGN KEY (`match_id`)
REFERENCES `matches` (`match_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3689891 DEFAULT CHARSET=utf8

以下查詢占用1秒以上,對於我這么簡單的事情來說,這似乎很慢:

SELECT SQL_NO_CACHE COUNT(*) AS match_count
FROM matches INNER JOIN matches_heroes ON matches.match_id = matches_heroes.match_id
WHERE hero_id = 5

僅刪除WHERE子句沒有幫助,但如果我也取出INNER JOIN,如下所示:

SELECT SQL_NO_CACHE COUNT(*) AS match_count FROM matches

......只需0.05秒。 似乎INNER JOIN非常昂貴。 我對連接沒有多少經驗。 這是正常的還是我做錯了什么?

更新#1:這是EXPLAIN結果。

id  select_type  table          type   possible_keys                     key     key_len  ref                                rows  Extra  
1   SIMPLE       matches_heroes ref    match_id,hero_id,match_id_hero_id hero_id 2        const                              34742
1   SIMPLE       matches        eq_ref PRIMARY                           PRIMARY 8        mydatabase.matches_heroes.match_id 1     Using index

更新#2:在聽完你們之后,我認為它運作正常,而且速度和它一樣快。 如果您不同意,請告訴我。 謝謝你的幫助。 對此,我真的非常感激。

使用 COUNT(matches.match_id)而不是 count(*) ,因為當使用連接時,最好不要使用 *因為它會進行額外的計算。 使用聯接中的列是確保您不請求任何其他操作的最佳方法。 (MySql InnerJoin不是問題,我的不好)。

此外,您應該驗證是否已對所有密鑰進行碎片整理,並且有足夠的RAM可供索引在內存中加載

更新1:


嘗試為match_id,hero_id添加組合索引match_id,hero_id因為它應該提供更好的性能。

ALTER TABLE `matches_heroes` ADD KEY `match_id_hero_id` (`match_id`,`hero_id`)


更新2:


我對接受的答案感到不滿意,mysql對於2毫米的記錄來說速度很慢,而且我的ubuntu PC(i7處理器,帶標准硬盤)上的基准測試也沒有。

-- pre-requirements

CREATE TABLE seq_numbers (
    number INT NOT NULL
) ENGINE = MYISAM;


DELIMITER $$
CREATE PROCEDURE InsertSeq(IN MinVal INT, IN MaxVal INT)
    BEGIN
        DECLARE i INT;
        SET i = MinVal;
        START TRANSACTION;
        WHILE i <= MaxVal DO
            INSERT INTO seq_numbers VALUES (i);
            SET i = i + 1;
        END WHILE;
        COMMIT;
    END$$
DELIMITER ;

CALL InsertSeq(1,200000)
;

ALTER TABLE seq_numbers ADD PRIMARY KEY (number)
;

--  create tables

-- DROP TABLE IF EXISTS `matches`
CREATE TABLE `matches` (
`match_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`start_time` int(10) unsigned NOT NULL,
PRIMARY KEY (`match_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
;

CREATE TABLE `heroes` (
`hero_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`name` char(40) NOT NULL,
PRIMARY KEY (`hero_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
;

CREATE TABLE `matches_heroes` (
`match_id` bigint(20) unsigned NOT NULL,
`hero_id` smallint(6) unsigned NOT NULL,
PRIMARY KEY (`match_id`,`hero_id`),
KEY (match_id),
KEY (hero_id),
CONSTRAINT `matches_heroes_ibfk_2` FOREIGN KEY (`hero_id`) REFERENCES `heroes` (`hero_id`),
CONSTRAINT `matches_heroes_ibfk_1` FOREIGN KEY (`match_id`) REFERENCES `matches` (`match_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=MyISAM DEFAULT CHARSET=utf8
;
-- insert DATA
-- 100
INSERT INTO heroes(name)
SELECT SUBSTR(CONCAT(char(RAND()*25+65),char(RAND()*25+97),char(RAND()*25+97),char(RAND()*25+97),char(RAND()*25+97),char(RAND()*25+97),char(RAND()*25+97),char(RAND()*25+97),char(RAND()*25+97),char(RAND()*25+97),char(RAND()*25+97),char(RAND()*25+97)),1,RAND()*9+4) as RandomName
FROM seq_numbers WHERE number <= 100

-- 200000
INSERT INTO matches(start_time)
SELECT rand()*1000000
FROM seq_numbers WHERE number <= 200000

-- 2000000
INSERT INTO matches_heroes(hero_id,match_id)
SELECT a.hero_id, b.match_id
FROM heroes as a
INNER JOIN matches as b ON 1=1
LIMIT 2000000

-- warm-up database, load INDEXes in ram (optional, works only for MyISAM tables)
LOAD INDEX INTO CACHE matches_heroes,matches,heroes


-- get random hero_id
SET @randHeroId=(SELECT hero_id FROM matches_heroes ORDER BY rand() LIMIT 1);


-- test 1 

SELECT SQL_NO_CACHE @randHeroId,COUNT(*) AS match_count
FROM matches as a 
INNER JOIN matches_heroes as b ON a.match_id = b.match_id
WHERE b.hero_id = @randHeroId
; -- Time: 0.039s


-- test 2: adding some complexity 
SET @randName = (SELECT `name` FROM heroes WHERE hero_id = @randHeroId LIMIT 1);

SELECT SQL_NO_CACHE @randName, COUNT(*) AS match_count
FROM matches as a 
INNER JOIN matches_heroes as b ON a.match_id = b.match_id
INNER JOIN heroes as c ON b.hero_id = c.hero_id
WHERE c.name = @randName
; -- Time: 0.037s

結論:測試結果快了大約20倍,測試之前我的服務器負載大約是80%,因為它不是專用的mysql服務器並且運行了其他cpu密集型任務,所以如果你運行整個腳本(從上面)並獲得較低的結果可能是因為:

  1. 你有一個共享主機,負載太大。 在這種情況下,您無能為力:您要么向當前主機投訴,要么為更好的主機/虛擬機付費,要么嘗試其他主機
  2. 您配置的key_buffer_size (對MyISAM)或innodb_buffer_pool_size (對於InnoDB)太小,最佳規模將超過150MB
  3. 你的可用ram是不夠的,你需要大約100 - 150 mb的ram才能將索引加載到內存中。 解決方案:釋放一些ram或購買更多

請注意,通過使用測試腳本,生成新數據可以排除索引碎片問題。 希望這會有所幫助,並詢問您是否在測試時遇到問題。


OBS:


SELECT SQL_NO_CACHE COUNT(*) AS match_count 
FROM matches INNER JOIN matches_heroes ON matches.match_id = matches_heroes.match_id 
WHERE hero_id = 5` 

相當於:

SELECT SQL_NO_CACHE COUNT(*) AS match_count 
FROM matches_heroes 
WHERE hero_id = 5` 

所以你不需要加入,如果這是你需要的數量,但我猜這只是一個例子。

所以你說閱讀200,000條記錄的表比讀取2,000,000條記錄的表更快,找到所需的記錄,然后全部用它們來查找200,000條記錄表中的匹配記錄?

這讓你感到驚訝嗎? 這對dbms來說只是做了很多工作。 (甚至可以說,當dbms認為全表掃描速度更快時,dbms決定不使用hero_id索引。)

所以在我看來,這里發生的事情並沒有錯。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM