具有 SELECT 性能问题的巨大 innodb 表

Huge innodb tables with SELECT performance issue

我有两个巨大的 innodb tables(page:+40M 行,+30Gb 和 stat:+45M 行,+10Gb)。我有一个从这两个 table 的连接中选择行的查询,它过去需要大约一秒钟的时间来执行。最近,完成完全相同的查询需要超过 20 秒(有时长达几分钟)。我怀疑有很多插入和更新可能需要优化。我 运行 OPTIMIZE TABLE 在 table 上使用 phpMyAdmin 但没有任何改进。我用 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

这些是 table 的结构:

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;

如 phpMyAdmin 所述,以下查询仅用了 0.0126 秒,总结果为 38685601:

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'

我是不是漏掉了什么?有人可以帮忙吗?

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

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

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

运行

+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 报告中看到 "Using index"。

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

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

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