【问题标题】:MySQL PDO query optimizationMySQL PDO 查询优化
【发布时间】:2013-09-06 02:50:13
【问题描述】:

我正在尝试减少此脚本的执行时间。它在一个循环中查询大约 1000 次大约 200 万条记录的数据库:

foreach ($ids as $id){
    $stmt=$dbh->query("SELECT SQL_CACHE * FROM `ids` 
                WHERE $id BETWEEN `id_start` AND `id_end`");
    $rows[] = $stmt->fetch();
}

在 4 核 8 GB 机器上需要很长时间(大约 800 秒!)。 id 组不重叠,id 在每次执行中往往来自几个不同的组,我已经索引了 (id_start,id_end) 和 (id_end)。

缓存极大地改善了这种情况(多次运行相同的 1000 个值只需几秒钟),但我想知道如何加快非缓存查询的速度。

EXPLAIN 的示例输出:

"id"    "select_type"   "table"     "type"  "possible_keys"     "key"               "key_len"   "ref"   "rows"  "Extra"
"1"     "SIMPLE"        "ids"       "range" "id_start,id_end"   "id_start,id_end"   "5"          ""     "52508" "Using index condition"

编辑:有时我得到“使用位置”而不是“使用索引条件”(不确定,但我认为来自高于 840771583 的 id 值)为什么?

编辑 2:完整的创建代码:

CREATE TABLE `ids` (
    `id_start` INT(10) UNSIGNED NULL DEFAULT NULL,
    `id_end` INT(10) UNSIGNED NULL DEFAULT NULL,
    `iso-639-1` VARCHAR(2) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
    `country_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
    `region_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
    `city_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
    `area_code` VARCHAR(16) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
    `timezone` VARCHAR(6) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
    UNIQUE INDEX `id_startid_end` (`id_start`, `id_end`),
    INDEX `id_end` (`id_end`),
    INDEX `country_name` (`country_name`),
    INDEX `region_name` (`region_name`),
    INDEX `city_name` (`city_name`),
    INDEX `area_code` (`area_code`),
    INDEX `iso-639-1` (`iso-639-1`),
    INDEX `timezone` (`timezone`)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB;

【问题讨论】:

  • EXPLAIN 对查询有何看法?它使用索引吗?
  • @Barmar 是的,看起来确实如此。我编辑添加了EXPLAIN的输出。
  • 这个表是只读表吗?假设“读/写拆分”。它使用哪种存储引擎?
  • 您真的应该在循环之外准备查询并将$id 绑定为参数。一条语句执行多次总是更好
  • 间隔(id_start,id_end) 是否重叠,即查询是否有可能返回多行?如果它们不重叠,请在此处查看我的答案,在一个几乎相同的问题中:Slow Query Gets Even Slower After Indexing

标签: php mysql sql pdo


【解决方案1】:

由于间隔不重叠,请尝试重写查询:

SELECT * 
FROM ids
WHERE id_start =
      ( SELECT MAX(id_start) 
        FROM ids
        WHERE id_start <= $id
      )
  AND $id <= id_end ;

【讨论】:

  • 哇!!!魔法!!! 1000 次查询仅需 1 秒!你是怎么做到的?这里发生了什么?
  • 抱歉,今天时间不多。我可以在今晚或明天晚些时候用解释来扩展答案。
  • 在浏览了您在第一条评论中发布的链接后,我看到在原始查询中,总共 1822141 中只有 52508 行(我猜是范围内的行)表具有的行。您提出的查询使用索引 fseek,当只有少量行要检查时,它比顺序索引 fscan 快得多,因为尽管在读取连续行时速度较慢,但​​它可以跳过行。我对吗?我还没有明白为什么重叠很重要,但我正在努力实现它:-)
  • 是的,正确,子查询执行索引搜索。 EXPLAIN 应该显示“选择优化掉的表”,这意味着 MAX() 仅使用索引查找找到。有关详细信息,请参阅EXPLAIN Output Format。唯一性是必需的,因此我们确信它确实会返回我们需要的值(如果存在匹配间隔。)它还保证外部查询 - 尽管它进行范围检查 - 也只会返回一行。跨度>
【解决方案2】:
It queries a database with about 2 million records about 1000 times on a loop:
                                                         ^^^^^^^^^^^^^^^^^^^^   

这里

是你的问题。

肯定应该是单个查询。

还可以考虑调整 mysql 守护进程,以确保密钥缓冲区大小是否足够。

顺便说一句,这个问题与 PDO 完全无关。处理问题时,您必须尽可能缩小范围,去掉所有不必要的部分。说到查询,您必须将其带入控制台并在那里播放。

【讨论】:

  • 我指定我正在使用 PDO,以防驱动程序可以提供某种形式的优化(例如我尝试但没有改进任何东西的准备好的语句)。当然,我从控制台处理我的查询。我将尝试形成对多个 id 进行分组的大查询,但输出必须按 id 排序,所以我不知道这是否会更好......
  • 只使用一个查询它花费的时间几乎没有,即使没有订购它仍然超过 5 分钟的查询有 1000 个请求,不知何故我得到少于 1000 行,所以查询甚至不工作正确(我使用相同的查询,但使用WHERE $id BETWEEN id_start AND id_end OR $id2 BETWEEEN id_start AND id_end 等代替)。我还增加了 innodb 缓冲池的大小,但没有运气,可能是因为这篇文章说小型数据库已经完全缓存:lists.mysql.com/mysql/214401
猜你喜欢
  • 1970-01-01
  • 2011-01-22
  • 2011-07-07
  • 2018-12-21
  • 2010-12-15
  • 2011-11-04
相关资源
最近更新 更多