简体   繁体   English

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

[英]MYSQL: query doesn't use the index some time

Database of test: 测试数据库:

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');

Depending with the date passed, sometimes MYSQL doesn't use the INDEX. 取决于日期,有时MYSQL不使用INDEX。

I know that I need to add / edit INDEX, please, could you please help me to perform this query very well? 我知道我需要添加/编辑INDEX,请您能帮我很好地执行此查询吗?

This query doesn't use the 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 link: http://sqlfiddle.com/#!2/059d8/7/0 解释链接: http ://sqlfiddle.com/#!2/059d8/7/0

This query use it: 此查询使用它:

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

Change only the year. 仅更改年份。

But the biggest problem is that in production environment, with ~65.000 rows of transactions, query hangs on over 60 seconds (!) 但是最大的问题是,在生产环境中,有大约65.000行事务,查询挂起超过60秒(!)。

I created a sqlfiddle, this is the link: http://sqlfiddle.com/#!2/059d8/1/0 我创建了一个sqlfiddle,这是链接: http ://sqlfiddle.com/#!2/059d8/1/0

Thank you very much! 非常感谢你!

Add the following two indexes: 添加以下两个索引:

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

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

This allows all the tables to be accessed by covering indexes, and eliminates some of the ALL type tables. 这允许通过覆盖索引来访问所有表,并消除了一些ALL类型表。 See the SQLfiddle for details: http://sqlfiddle.com/#!2/b11bb/4 有关详细信息,请参见SQLfiddle: http ://sqlfiddle.com/#!2/b11bb/4

Also, consider upgrading to 5.6, to get rid of the "using join buffer". 另外,考虑升级到5.6,以摆脱“使用连接缓冲区”。

This is interesting. 这是有趣的。 I played with the dates, and if the filter is obviously off (using year 2001 for example) mysql uses its CONST tables to compute the query: 我使用日期,如果过滤器明显关闭(例如,使用2001年),mysql将使用其CONST表来计算查询:

Impossible WHERE noticed after reading const tables

I suspect there's a strong optimization on the date columns which I guess is interfering with the index calculations. 我怀疑日期列有一个强大的优化,我认为这会干扰索引计算。 But I'm not sure about this... 但是我不确定...

Nonetheless, your query can be improved. 尽管如此,您的查询还是可以改进的。

Take a look at this one: 看一下这个:

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;

I moved in the subquery the COUNT and the WHERE clause, so you only have to use the transactions table once. 我在子查询中移动了COUNTWHERE子句,因此您只需要使用事务表一次。 But it means that the meaning of the query changed, you have to check if it is what you want. 但这意味着查询的含义已更改,您必须检查它是否是您想要的。 Now, the count will count only the transactions between these 2 dates while before, it was counting them in general for the given user, regardless of the date. 现在,该计数将仅计算这两个日期之间的交易,而之前,该计数通常针对给定用户对它们进行计数,而与日期无关。 If you don't think it fits your needs, just ignore my change. 如果您认为它不适合您的需求,请忽略我的更改。

From the DDL perspective, I think you can improve it like this: 从DDL的角度来看,我认为您可以像这样改善它:

  1. IF and only IF, you have many different user accounts (cardinality of idAccount >20-30), spread more or less equally: 如果只有IF,您就有许多不同的用户帐户(idAccount的基数> 20-30),或多或少地平均分配:

KEY index_idAccount ( idAccount ) on the user table. user表上的键index_idAccountidAccount )。

2. Change your existing index index_dateTransaction to use the idUser too: 2.更改现有索引index_dateTransaction以也使用idUser:

KEY index_dateTransaction ( idUser , dateTransaction ) KEY index_dateTransactionidUserdateTransaction

Final result would be as follows: 最终结果如下:

在此处输入图片说明

您应该在transactions.idUser,users.idUser和transactions.dateTransaction上具有索引

If I understand you right, you need the dates of the first and last transaction for each user with account = 1, plus the total number of the user's transactions within a certain period. 如果我理解正确,那么您需要每个帐户= 1的用户的第一次和最后一次交易的日期,以及该用户在一定时期内的交易总数。

This is best done like this: 最好这样做:

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

Create the following indexes: 创建以下索引:

users (idAccount)
transactions (idUser, dateTransaction)

I don't include the primary keys into the indexes which I should have done on MyISAM tables, however, you should not use MyISAM unless you have a specific reason for that (which I don't think you have). 我没有将主键包含在我应该在MyISAM表上完成的索引中,但是,除非有特殊原因(我认为您没有),否则不应使用MyISAM。 Change your engine to InnoDB. 将引擎更改为InnoDB。

See this fiddle: http://sqlfiddle.com/#!2/d92e6/3 看到这个小提琴: http ://sqlfiddle.com/#!2/ d92e6/3

On a side note, if this query is frequent, you should consider materializing some of its results. 附带说明一下,如果此查询很频繁,则应考虑实现其某些结果。 If you keep the daily or monthly transaction counts per user in a separate table which would be updated with a trigger, the most costly part of your query, the COUNT , would go away, which would improve the query greatly. 如果将每个用户的每日或每月交易计数保存在单独的表中,该表将通过触发器进行更新,则查询中最昂贵的部分COUNT将消失,这将大大改善查询。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM