MYSQL:查询一段时间内不使用索引

时间:2014-10-10 19:34:27

标签: mysql sql

测试数据库:

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。

我知道我需要添加/编辑INDEX,请你帮我完成这个查询吗?

此查询使用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

非常感谢!

4 个答案:

答案 0 :(得分: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,以摆脱“使用连接缓冲区”。

答案 1 :(得分: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. IF并且只有IF,您有许多不同的用户帐户(idAccount的基数> 20-30),或多或少均等地传播:
  2.   

    index_idAccount表上的idAccount user}。

    2。 更改现有索引index_dateTransaction以使用idUser:

      

    KEY index_dateTransactionidUserdateTransaction

    最终结果如下:

    enter image description here

答案 2 :(得分:0)

您应该在transactions.idUser,users.idUser和transactions.dateTransaction

上有索引

答案 3 :(得分: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将会消失,这将极大地改善查询。 / p>