奇怪的MySQL性能问题

时间:2014-03-05 22:24:29

标签: mysql performance innodb

任何人都可以在下面的查询中解释为什么我按原样运行它,需要2秒,如果我从select中删除“users.login”需要0.0秒。此外,如果我留下“users.login”并删除“users.usertypesid”,它还需要0.0秒。因此,只有选择了login和usertypesid,才能获得2秒的执行时间。 我不明白,因为这些字段是数字和简单的varchar,它们不会在where子句或order by中进行比较,排序或使用。我甚至尝试从表中删除所有TEXT列(因为我认为可能缓冲行以某种方式超出行)但是没有任何变化。 最奇怪的是,items和itemdescs是包含数千条记录的表,如果我省略或添加这些表中的一些字段(从itemdescs中选择几个字段而不是itemdescs。,或者还包括items。)性能总是一样的。但是表“users”只有5条记录,如果我选择的字段是数值(usertypesid),或者登录性能在8核心最新硬件上从0.00秒下降到2秒。 5个整数或5个字符串,每个8个字符的长度如何使该硬件处理它2秒钟?同样,这些字段从不用于排序或其他任何东西(比较等),只包含在select中!在这些字段上添加/删除索引也没有任何改变。这是查询:

select SQL_NO_CACHE itemdescs.*, users.id, users.usertypesid, users.login
  from items, itemdescs, langs, users
 where itemdescs.itemsid=items.id 
   and itemdescs.langsid=langs.id
   and langs.tag='en'
   and items.usersid=users.id
 order by activationdate desc
 limit 0,8

以下是表格定义。

CREATE TABLE `items` (
    `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `usersid` INT(10) UNSIGNED NOT NULL DEFAULT '0',
    `activestatesid` INT(10) UNSIGNED NOT NULL DEFAULT '0',
    `activationdate` DATE NOT NULL DEFAULT '2013-01-01',
    PRIMARY KEY (`id`),
    INDEX `usersid` (`usersid`),
    INDEX `activestatesid` (`activestatesid`),
    INDEX `activationdate` (`activationdate`)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=31065;

CREATE TABLE `itemdescs` (
    `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `itemsid` INT(10) UNSIGNED NOT NULL DEFAULT '0',
    `langsid` INT(10) UNSIGNED NOT NULL DEFAULT '0',
    `name` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
    `longdesc` VARCHAR(6000) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
    `synonyms` VARCHAR(500) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
    `shortdesc` VARCHAR(500) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
    `mediumdesc` VARCHAR(1000) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
    PRIMARY KEY (`id`),
    UNIQUE INDEX `itemsid_langsid` (`itemsid`, `langsid`),
    INDEX `itemsid` (`itemsid`),
    INDEX `langid` (`langsid`),
    INDEX `name` (`name`)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
ROW_FORMAT=COMPACT
AUTO_INCREMENT=632465;

CREATE TABLE `langs` (
    `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(50) NOT NULL DEFAULT '0' COLLATE 'utf8_unicode_ci',
    `tag` VARCHAR(50) NOT NULL DEFAULT '0' COLLATE 'utf8_unicode_ci',
    PRIMARY KEY (`id`),
    INDEX `tag` (`tag`)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=19;

CREATE TABLE `users` (
    `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `usertypesid` INT(10) UNSIGNED NOT NULL DEFAULT '0',
    `login` VARCHAR(50) NULL DEFAULT '0' COLLATE 'utf8_unicode_ci',
    PRIMARY KEY (`id`),
    INDEX `usertypesid` (`usertypesid`)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
ROW_FORMAT=COMPACT
AUTO_INCREMENT=5;

以下是按要求解释的结果:

慢一个(带登录字段): http://i.stack.imgur.com/bedvf.jpg

速度快: http://i.stack.imgur.com/xjssj.jpg

由于声誉低于10,我无法发布图片。

更新: 如果我重写这样的查询,我得到相同的结果,它立即工作(0.000秒):

select SQL_NO_CACHE 
*
from
(select items.usersid, itemdescs.*
  from items, itemdescs, langs
 where itemdescs.itemsid=items.id 
   and itemdescs.langsid=langs.id
   and langs.tag='en'
 order by activationdate desc
 limit 0,8) nousers, users
 where
 nousers.usersid=users.id

现在这样可行,但是没有解释为什么只有5条记录的表使查询变慢(在第一个例子中),而有数千行的表不影响它?我怎么知道何时重写这样的查询以及要注意哪个表?

4 个答案:

答案 0 :(得分:0)

尝试这三件事,看看它是否能解决问题。 1)转换为ANSI SQL; 2)摆脱已知不会引起问题的一切; 3)删除提示。

即使ANSI SQL和你的SQL应该是等价的,我也发现了Oracle& MySQL以不同的方式处理查询。也许使用ANSI查询可以避免减速。这是查询的精简版本,转换为ANSI。

select users.usertypesid, users.login
from items
 join itemdescs on itemdescs.itemsid = items.id
 join langs on langs.id = itemdescs.langsid
 join users on users.id = items.usersid
where langs.tag='en'
order by activationdate desc;

答案 1 :(得分:0)

使用索引时,您需要找到一个很好的平衡,如果您已经遇到SELECTS问题,这些表对于INSERTS,DELETES和UPDATES来说会非常慢。有很多索引,索引文件会变得相当大。尝试删除仅保留用于主键和联接的索引。

要考虑的另一件事是将SELECT中的语法更改为更新的连接方法。

SELECT SQL_NO_CACHE itemdescs.*, users.id, users.usertypesid, users.login 
FROM itemdescs
INNER JOIN items ON itemdescs.itemsid = items.id 
INNER JOIN users ON itemdescs.usersid = users.id
INNER JOIN langs ON itemdescs.langsid = langs.id
WHERE langs.tag = 'en'
ORDER BY items.activationdate
LIMIT 0, 8;

这里INDEXES的最佳候选人是:

items.id
users.id
langs.id
itemsdescs.itemsid
itemdescs.usersid
itemsdescs.langsid
items.activationdate

用于搜索,排序或分组的索引列,而不是您为输出选择的列。

答案 2 :(得分:0)

我同意其他两个答案的评论,但会扩展一些其他元素......你应该考虑“覆盖索引”,以便你的教育。引擎只会在每个表中使用一个最适合正在执行的查询的索引。也就是说,对于tg,items,langs有三个单独的索引将是一个糟糕的选择。相反,它应该是一个具有所有查询标准的SINGLE索引,并且可以显着提高性能......

为了防止需要返回原始数据页面,覆盖索引包括所需的字段。我会为各自的表提供以下索引。

table         index
users         ( id, usertypesid, login)
items         ( id, usersid, activationdate desc )
langs         ( id, tag )
itemdescs     ( langsid, activationdate desc, itemsid )

和查询

select SQL_NO_CACHE 
      itemdescs.*, 
      users.id, 
      users.usertypesid, 
      users.login
   from 
      itemdescs
         JOIN langs
            on itemdescs.langsid = langs.id
            and langs.tag = 'en'
         JOIN items
            on itemdescs.itemsid = items.id 
            JOIN users
               items.usersid = users.id
   order by
      items.activationdate desc
   limit 
      0,8

另一个可能的优化是关键字“STRAIGHT_JOIN”,它告诉MySQL引擎按照您的布局顺序进行查询。有时这可以显着帮助查询运行。如果是这样,只需改变......

select SQL_NO_CACHE 

select STRAIGHT_JOIN SQL_NO_CACHE 

答案 3 :(得分:0)

最终解决方案是在项目表上使用强制索引。我添加了索引adate(activationdate desc)并且MySQL没有使用它,因此查询仍然很慢(大约2秒)。在我强迫索引之后:

select SQL_NO_CACHE itemdescs.*, users.id, users.usertypesid, users.login
  from items force index(adate), itemdescs, langs, users
 where itemdescs.itemsid=items.id 
   and itemdescs.langsid=langs.id
   and langs.tag='en'
   and items.usersid=users.id
 order by activationdate desc
 limit 0,8

查询在0.000秒内运行。有谁知道为什么MySQL无法弄清楚它需要使用这个索引?此外,因为我想把这部分放在视野中:

select SQL_NO_CACHE itemdescs.*, users.id, users.usertypesid, users.login
  from items force index(adate), itemdescs, langs, users
 where itemdescs.itemsid=items.id 
   and itemdescs.langsid=langs.id
   and langs.tag='en'
   and items.usersid=users.id

后来从客户端称之为(实际上已经在客户端,但性能不佳):

select * from myview (items force index(adate)) where DYNAMIC CONDITIONS GO HERE order by itemsactivationdate desc limit 0,10

然而,MySQL中没有这样的语法。那么有谁知道我该怎么做,或者甚至可能吗?如果不是很头疼,因为客户端应用程序一直使用视图,如果我不能对它们使用强制索引并且MySQL查询计划不好(情况就是如此)除了所有客户端上的重写代码之外基本上没有其他解决方案?我可以以某种方式重写查询,以便它使用正确的索引而不使用“强制索引”吗?