![](/img/trans.png)
[英]MySQL - Why in this case a secondary index doesn't reduce the time of this query?
[英]MYSQL: query doesn't use the index some time
测试数据库:
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
解释链接: 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
非常感谢你!
添加以下两个索引:
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,以摆脱“使用连接缓冲区”。
这是有趣的。 我使用日期,如果过滤器明显关闭(例如,使用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;
我在子查询中移动了COUNT
和WHERE
子句,因此您只需要使用事务表一次。 但这意味着查询的含义已更改,您必须检查它是否是您想要的。 现在,该计数将仅计算这两个日期之间的交易,而之前,该计数通常针对给定用户对它们进行计数,而与日期无关。 如果您认为它不适合您的需求,请忽略我的更改。
从DDL的角度来看,我认为您可以像这样改善它:
user
表上的键index_idAccount
(idAccount
)。
2.更改现有索引index_dateTransaction
以也使用idUser:
KEY
index_dateTransaction
(idUser
,dateTransaction
)
最终结果如下:
您应该在transactions.idUser,users.idUser和transactions.dateTransaction上具有索引
如果我理解正确,那么您需要每个帐户= 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
将消失,这将大大改善查询。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.