【问题标题】:Huge innodb tables with SELECT performance issue具有 SELECT 性能问题的巨大 innodb 表
【发布时间】:2017-05-07 12:41:25
【问题描述】:

我有两个巨大的 innodb 表(page:+40M 行,+30Gb 和stat:+45M 行,+10Gb)。我有一个从这两个表的连接中选择行的查询,它曾经需要大约一秒钟的时间来执行。最近,完成完全相同的查询需要 20 多秒(有时长达几分钟)。我怀疑有很多插入和更新可能需要优化。我使用 phpMyAdmin 在桌子上运行了OPTIMIZE TABLE,但没有任何改进。我在 Google 上搜索了很多,但找不到任何可以帮助我解决这种情况的内容。

我之前提到的查询如下所示:

SELECT `c`.`unique`, `c`.`pub`
    FROM `pages` `c`
    LEFT JOIN `stat` `s` ON `c`.`unique`=`s`.`unique`
    WHERE `s`.`isc`='1'
      AND `s`.`haa`='0'
      AND (`pubID`='24')
    ORDER BY `eid` ASC LIMIT 0, 10

这些是表结构:

CREATE TABLE `pages` (
  `eid` int(10) UNSIGNED NOT NULL,
  `ti` text COLLATE utf8_persian_ci NOT NULL,
  `fat` text COLLATE utf8_persian_ci NOT NULL,
  `de` text COLLATE utf8_persian_ci NOT NULL,
  `fad` text COLLATE utf8_persian_ci NOT NULL,
  `pub` varchar(100) COLLATE utf8_persian_ci NOT NULL,
  `pubID` int(10) UNSIGNED NOT NULL,
  `pubn` text COLLATE utf8_persian_ci NOT NULL,
  `unique` tinytext COLLATE utf8_persian_ci NOT NULL,
  `pi` tinytext COLLATE utf8_persian_ci NOT NULL,
  `kw` text COLLATE utf8_persian_ci NOT NULL,
  `fak` text COLLATE utf8_persian_ci NOT NULL,
  `te` text COLLATE utf8_persian_ci NOT NULL,
  `fae` text COLLATE utf8_persian_ci NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_persian_ci;
ALTER TABLE `pages`
  ADD PRIMARY KEY (`eid`),
  ADD UNIQUE KEY `UNIQ` (`unique`(128)),
  ADD KEY `pub` (`pub`),
  ADD KEY `unique` (`unique`(128)),
  ADD KEY `pubID` (`pubID`) USING BTREE;
ALTER TABLE `pages` ADD FULLTEXT KEY `faT` (`fat`);
ALTER TABLE `pages` ADD FULLTEXT KEY `faA` (`fad`,`fae`);
ALTER TABLE `pages` ADD FULLTEXT KEY `faK` (`fak`);
ALTER TABLE `pages` ADD FULLTEXT KEY `pubn` (`pubn`);
ALTER TABLE `pages` ADD FULLTEXT KEY `faTAK` (`fat`,`fad`,`fak`,`fae`);
ALTER TABLE `pages` ADD FULLTEXT KEY `ab` (`de`,`te`);
ALTER TABLE `pages` ADD FULLTEXT KEY `Ti` (`ti`);
ALTER TABLE `pages` ADD FULLTEXT KEY `Kw` (`kw`);
ALTER TABLE `pages` ADD FULLTEXT KEY `TAK` (`ti`,`de`,`kw`,`te`);
ALTER TABLE `pages`
  MODIFY `eid` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;


CREATE TABLE `stat` (
  `sid` int(10) UNSIGNED NOT NULL,
  `unique` tinytext COLLATE utf8_persian_ci NOT NULL,
  `haa` tinyint(1) UNSIGNED NOT NULL,
  `isc` tinyint(1) NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_persian_ci;
ALTER TABLE `stat`
  ADD PRIMARY KEY (`sid`),
  ADD UNIQUE KEY `Unique` (`unique`(128)),
  ADD KEY `isc` (`isc`),
  ADD KEY `haa` (`haa`),
ALTER TABLE `stat`
  MODIFY `sid` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

下面的查询只用了 0.0126 秒,总结果为 38685601,如 phpMyAdmin 所说:

SELECT `sid` FROM `stat` WHERE `s`.`isc`='1' AND `s`.`haa`='0'

这一次耗时 0.0005 秒,总共有 5159484 个结果

SELECT `eid`, `unique`, `pubn`, `pi` FROM `pages` WHERE `pubID`='24'

我错过了什么吗?有人可以帮忙吗?

【问题讨论】:

  • 帮助你基于什么?您没有分享任何能让我们帮助您的具体信息。
  • 我已经添加了查询和表结构。希望这些帮助。
  • 我看到独特的是 tinytext。您可以添加自动增量主键吗?这对加入有很大帮助
  • 如果可以的话,至少我会使用 varchar,因为 varchar 与行一起存储,而不是 tinytext 与我理解的内容分开存储
  • 您显示表定义的方式很奇怪,并且有错误。为什么不能运行SHOW CREATE TABLE 来显示final 表?

标签: mysql select optimization innodb


【解决方案1】:

速度变慢可能是由于扫描了太多行,而现在这已经超出了缓存的容量。所以,让我们尝试改进查询。

  • INDEX(pubID) 替换为INDEX(pubID, eid) -- 这可能允许索引处理WHEREORDER BY,从而避免排序。
  • TINYTEXT 替换为VARCHAR(255) 或更小的限制。这可能会加快 tmp 表的速度。
  • 不要在eid 上使用前缀索引——它是INT
  • 不要说UNIQUE 带有前缀——UNIQUE(x(128)) 只检查前 128 列的唯一性!
  • 更改为VARCHAR(255)(或更少)后,您可以将UNIQUE 应用于整个列。
  • 最大的性能问题是在两个表上进行过滤——您可以将状态标志移到主表中吗?
  • LEFT JOIN 更改为JOIN
  • unique 看起来像什么?如果它是“UUID”,那可以进一步解释问题。
  • 如果这是一个 39 个字符的 UUID,则可以将字符串转换为 16 字节的列,以进一步节省空间(和加速)。如有必要,让我们进一步讨论。

0.5 毫秒内的 500 万个结果是虚假的——它是从查询缓存中获取的。关闭 QC 或使用 SELECT SQL_NO_CACHE... 运行

【讨论】:

  • 非常有用的点。谢谢!在这里看到你的答案之前,我做了你的第六个建议,这似乎至少现在解决了这个问题。 eid 不应该出现在 UNIQ 中,而是在这里打错了。 unique 列不能指定长度,因为我无法控制其内容,它们可以是任意长度。
  • "可以是任意长度"?? TINYTEXT 限制为 255 个字节。
  • 哦! TINYTEXT 在某种程度上等于 VARCHAR(255),我错过了!值得在这些非常大的桌子上转换它吗?我可能需要几个小时才能完成转换!
  • 不,这可能不值得特别努力。但是如果你需要为其他事情做一个大的ALTER,包括这个。
  • 谢谢,我会把它放在我的待办事项列表中
【解决方案2】:

+1 给@RickJames 的回答,但我已经做了一个测试。

我还建议您不要将名称 unique 用作列名,因为它是 SQL 保留字。

ALTER TABLE pages 
  CHANGE `unique` objectId VARCHAR(128) NOT NULL COMMENT 'Document Object Identifier',
  DROP KEY pubId,
  ADD KEY bktest1 (pubId, eid, objectId, pub);

ALTER TABLE stat 
    CHANGE `unique` objectId VARCHAR(128) NOT NULL COMMENT 'Document Object Identifier',
    DROP KEY `unique`,
    ADD UNIQUE KEY bktest2 (objectId, isc, haa);

mysql> explain SELECT `c`.`objectId`, `c`.`pub`     FROM `pages` `c` JOIN `stat` `s` ON `c`.`objectId`=`s`.`objectId`     WHERE `s`.`isc`='1'       AND `s`.`haa`='0'       AND (`pubID`='24')     ORDER BY `eid` ASC LIMIT 0, 10;
+----+-------------+-------+------------+--------+-------------------------+---------+---------+-----------------------------+------+----------+--------------------------+
| id | select_type | table | partitions | type   | possible_keys           | key     | key_len | ref                         | rows | filtered | Extra                    |
+----+-------------+-------+------------+--------+-------------------------+---------+---------+-----------------------------+------+----------+--------------------------+
|  1 | SIMPLE      | c     | NULL       | ref    | unique,unique_2,bktest1 | bktest1 | 4       | const                       |    1 |   100.00 | Using where; Using index |
|  1 | SIMPLE      | s     | NULL       | eq_ref | bktest2,haa,isc         | bktest2 | 388     | test.c.objectId,const,const |    1 |   100.00 | Using index              |
+----+-------------+-------+------------+--------+-------------------------+---------+---------+-----------------------------+------+----------+--------------------------+

通过创建多列索引,这使它们覆盖索引,您会在 EXPLAIN 报告中看到“使用索引”。

eid 放在 bktest1 索引的第二位很重要,这样可以避免文件排序。

这是您希望在不对表进行非规范化或分区的情况下优化此查询的最佳方法。

接下来,您应该确保您的缓冲池足够大以容纳所有请求的数据。

【讨论】:

猜你喜欢
  • 2015-12-16
  • 2021-09-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-24
相关资源
最近更新 更多