2014-10-10 259 views
2

數據庫:MYSQL:查詢不使用索引測試一段時間

SET NAMES utf8; 
SET foreign_key_checks = 0; 
SET time_zone = '+02:00'; 
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; 

CREATE TABLE `account` (
    `idAccount` int(11) NOT NULL AUTO_INCREMENT, 
    `name` varchar(128) NOT NULL, 
    PRIMARY KEY (`idAccount`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

CREATE TABLE `users` (
    `idUser` int(11) NOT NULL AUTO_INCREMENT, 
    `idAccount` int(11) NOT NULL, 
    `firstName` varchar(128) NOT NULL, 
    PRIMARY KEY (`idUser`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 


DROP TABLE IF EXISTS `transactions`; 
CREATE TABLE `transactions` (
    `idTransactions` int(11) NOT NULL AUTO_INCREMENT, 
    `idUser` int(11) NOT NULL, 
    `dateTransaction` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 
    PRIMARY KEY (`idTransactions`), 
    KEY `index_dateTransaction` (`dateTransaction`) USING BTREE 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 


INSERT INTO `transactions` (`idTransactions`, `idUser`, `dateTransaction`) VALUES 
(1, 1, '2012-12-16 15:52:32'), 
(2, 1, '2012-12-20 15:52:37'), 
(3, 1, '2013-02-01 15:52:37'), 
(4, 2, '2013-03-16 15:52:37'), 
(5, 2, '2013-03-18 15:52:37'), 
(6, 3, '2014-04-19 15:52:37'), 
(7, 3, '2014-05-20 15:52:37'), 
(8, 4, '2014-06-21 15:58:46'); 

INSERT INTO `account` (`idAccount`, `name`) VALUES 
(1, 'Burger & Burger'); 

INSERT INTO `users` (`idUser`, `idAccount`, `firstName`) VALUES 
(1, 1, 'Roberto'), 
(2, 1, 'Alessandro'); 

根據同日通過,有時MySQL不使用索引。

我知道我需要添加/編輯索引,請你能幫我執行這個查詢很好嗎?

這個查詢使用INDEX:

SELECT 
    users.firstName, 
    ts1.*, 
    COUNT(transactions.dateTransaction) AS num_transactions 
FROM users 
    INNER JOIN transactions ON transactions.idUser = users.idUser 
    INNER JOIN ( 
     SELECT 
      users.idUser, 
      MIN(transactions.dateTransaction) AS first_transaction, 
      MAX(transactions.dateTransaction) AS last_transaction 
     FROM transactions 
      INNER JOIN users ON transactions.idUser = users.idUser 
     WHERE (users.idAccount = 1) 
     GROUP BY users.idUser 
    ) AS ts1 ON users.idUser = ts1.idUser 
WHERE 
    transactions.dateTransaction BETWEEN ('2012-01-01') AND ('2013-12-31') 
AND users.idAccount = 1 
GROUP BY users.idUser 

EXPLAIN鏈接:http://sqlfiddle.com/#!2/059d8/7/0

這個查詢使用它:只有

SELECT 
    users.firstName, 
    ts1.*, 
    COUNT(transactions.dateTransaction) AS num_transactions 
FROM users 
     INNER JOIN transactions ON transactions.idUser = users.idUser 
     INNER JOIN ( 
      SELECT 
       users.idUser, 
       MIN(transactions.dateTransaction) AS first_transaction, 
       MAX(transactions.dateTransaction) AS last_transaction 
      FROM transactions 
       INNER JOIN users ON transactions.idUser = users.idUser 
      WHERE users.idAccount = 1 
      GROUP BY users.idUser 
     ) AS ts1 ON users.idUser = ts1.idUser 
WHERE 
    transactions.dateTransaction BETWEEN ('2012-01-01') AND ('2012-12-31') 
AND users.idAccount = 1 
GROUP BY users.idUser 

變化的一年。 (!)

但最大的問題是,在生產環境中,用〜65.000行的交易,查詢60秒以上

我創建了一個sqlfiddle掛起,這是鏈接:http://sqlfiddle.com/#!2/059d8/1/0

謝謝你真是太棒了!

+1

請問您可以爲兩個查詢添加解釋計劃嗎? – Sebas 2014-10-10 19:38:45

+1

你的索引是如何定義的? – ericpap 2014-10-10 19:42:34

+2

有一個機會,如此大比例的記錄包含在第一範圍內,MySQL決定執行全表掃描更快。 – 2014-10-10 19:42:42

回答

2

添加以下兩個指標:

ALTER TABLE `users` ADD KEY `bk1_account_user` (idAccount, idUser); 

ALTER TABLE `transactions` KEY `bk2_user_datetrans` (idUser, dateTransaction); 

這允許通過覆蓋索引來訪問所有的表,並消除一些ALL型表。有關詳細信息,請參見SQLfiddle:http://sqlfiddle.com/#!2/b11bb/4

此外,請考慮升級到5.6,以擺脫「使用連接緩衝區」。

+0

使用您的INDEX似乎有助於很多我可憐的DB ;-)運行一些其他重測試,我會告訴你(並接受答案!)儘快。感謝您花費的時間! – sineverba 2014-10-11 05:56:15

+0

像超人一樣,當我們打電話給你時,你從遠方來:) – Sebas 2014-10-11 06:14:39

+0

@Bill我也有字段dateTransaction上的INDEX。我需要刪除它?這是實際情況:PRIMARY \t idTransactions INDEX \t dateTransaction INDEX \t idMemberCard,dateTransaction – sineverba 2014-10-11 11:09:15

0

你應該有transactions.idUser,users.idUser和transactions.dateTransaction

+0

哪種索引?你可以幫幫我嗎?我有一個主要的獨特,看看sqlfiddle。謝謝! – sineverba 2014-10-10 20:35:45

1

指標這很有趣。我打的日期,如果過濾器顯然是關閉(以2001年一年爲例),MySQL使用其CONST表來計算查詢:

Impossible WHERE noticed after reading const tables

我懷疑有一個在日期列強大的優化,其我猜是干擾了指數計算。但我不確定這...

儘管如此,您的查詢可以改進。

在這一個看看:

SELECT 
    users.firstName, 
    ts1.* 
FROM users 
    JOIN ( 
     SELECT 
      users.idUser, 
      MIN(transactions.dateTransaction) AS first_transaction, 
      MAX(transactions.dateTransaction) AS last_transaction, 
      COUNT(transactions.dateTransaction) AS num_transactions 
     FROM transactions 
      JOIN users ON transactions.idUser = users.idUser AND users.idAccount = 1 
     WHERE 
      transactions.dateTransaction BETWEEN ('2011-01-01') AND ('2011-07-31') 
     GROUP BY users.idUser 
    ) AS ts1 ON users.idUser = ts1.idUser 
WHERE 
    users.idAccount = 1 
GROUP BY users.idUser; 

我在子查詢的COUNTWHERE條款感動,所以你只需要使用事務表一次。但它意味着查詢的含義改變了,你必須檢查它是否是你想要的。現在,計數將只計算這兩個日期之間的交易,而在之前,計數通常針對給定的用戶,而不考慮日期。如果您認爲它不符合您的需求,請忽略我的更改。

從DDL的角度來看,我覺得你可以改善它是這樣的:

  1. 當且僅當,你有很多不同的用戶帳戶(idAccount> 20-30的基數),傳播或多或少同樣:
user

KEY index_idAccountidAccount)。

2. 更改現有索引index_dateTransaction使用ID用戶過多:

KEY index_dateTransactionidUserdateTransaction

最終的結果將是如下:

enter image description here

+0

我無法在子查詢中移動COUNT和WHERE。在這種模式下,如果用戶在2014-01-01上有first_transaction並且有BETWEEN 2014-02-01(例如),則2014-02-01(不正確)的結果將爲first_transaction。順便說一句,你和比爾把我放在正確的位置。我正在使用真正的數據庫,我會盡快告訴你。感謝您花時間和時間,我在等待檢查真正的DB首先接受答案; – sineverba 2014-10-11 05:54:59

+0

啊,爲了您的第一點,cardindality ATM超過100,所以我安全地添加了KEY提議。 – sineverba 2014-10-11 05:57:16

+1

@sineverba請考慮Bill對'(idAccount,idUser)'索引的建議,這似乎確實比'(idAccount)'更合適 – Sebas 2014-10-11 06:42:57

0

如果我理解你是正確的,那麼你需要賬戶= 1的每個用戶的第一次和最後一筆交易的日期加上一段時間內用戶交易的總數。

這是最好這樣做:

SELECT u.*, 
     (
     SELECT MIN(dateTransaction) 
     FROM transactions t 
     WHERE t.idUser = u.idUser 
     ) minDate, 
     (
     SELECT MAX(dateTransaction) 
     FROM transactions t 
     WHERE t.idUser = u.idUser 
     ) maxDate, 
     (
     SELECT COUNT(*) 
     FROM transactions t 
     WHERE t.idUser = u.idUser 
       AND t.dateTransaction BETWEEN '2012-01-01' AND '2012-02-02' 
     ) cnt 
FROM users u 
WHERE u.idAccount = 1 

創建以下指標:

users (idAccount) 
transactions (idUser, dateTransaction) 

我不包括主鍵到我應該在MyISAM表也做了索引,然而,除非你有特定的原因(我認爲你沒有),否則你不應該使用MyISAM。將您的引擎更改爲InnoDB。

看到這個小提琴:http://sqlfiddle.com/#!2/d92e6/3

在一個側面說明,如果此查詢頻繁,你應該考慮一些物化的結果。如果您將每個用戶的每日或每月交易計數保存在一個單獨的表格中,並使用觸發器進行更新,則查詢中成本最高的部分COUNT將消失,這將大大改善查詢。