使用两个索引列时缓慢的 mySQL 查询

Posted

技术标签:

【中文标题】使用两个索引列时缓慢的 mySQL 查询【英文标题】:Slow mySQL query when using two indexed columns 【发布时间】:2013-04-12 15:52:00 【问题描述】:

我有一个 mysql 表 (myISAM),其中包含大约 200 万行 - 姓名、地址、公司数据。名字和姓氏分别保存在不同的列中,因此我还有第二个表(由第一个表的主键链接),其中包含一个全名列。

第一个表中的名字、姓氏和公司名称(以及其他)被索引,辅助表中的全名列也是如此。

以此查询为起点:

SELECT * FROM table_a INNER JOIN table_b ON table_a.ID = table_b.ID WHERE....

在名称列上搜索完全匹配或什至类似之后的工作以毫秒为单位:

....table_a.first_name = 'Fred'
....table_a.surname = 'Bloggs'
....table_b.fullname = 'Fred Bloggs'
....table_a.first_name LIKE 'Mike%'

只是几个例子。

把公司名称也扔进去.....查询突然需要 15 到 20 秒:

....table_a.first_name = 'Fred' OR table_a.company_name = 'Widgets Inc'

例如

两个字段都已编入索引,完全匹配....为什么添加第二个索引搜索列会减慢速度?我错过了有关我的桌子设计的一些东西吗?

示例如下 - 还加入了一些其他表,但我不确定这些是否会影响性能: 在 0.0123 秒内返回的仅名称查询示例:

SELECT SQL_CALC_FOUND_ROWS 
    webmaster.dupe_master_id AS webmaster_id, 
    webmaster.first_name, 
    webmaster.family_name, 
    webmaster.job_title, 
    webmaster.company_name, 
    webmaster.address_1, 
    webmaster.address_2, 
    webmaster.town_city, 
    webmaster.state_county, 
    webmaster.post_code, 
    webmaster.email, 
    webmaster.ignored, 
    countries.country_name, 
    GROUP_CONCAT(DISTINCT titles.code ORDER BY code ASC) AS sub_string, 
    '' AS expo_string 
FROM 
    (`webmaster`) 
    LEFT JOIN `countries` ON `countries`.`country_id` = `webmaster`.`country_id` 
    LEFT JOIN `red_subscriptions` ON `red_subscriptions`.`webmaster_id` = `webmaster`.`webmaster_id` AND red_subscriptions.subscription_status_id = 2 
    LEFT JOIN `titles` ON `titles`.`title_id` = `red_subscriptions`.`title_id` 
    LEFT JOIN `webmaster_tags` ON `webmaster_tags`.`webmaster_id` = `webmaster`.`webmaster_id` 
    LEFT JOIN `tags` ON `tags`.`tag_id` = `webmaster_tags`.`tag_id` 
    INNER JOIN `webmaster_search_data` ON `webmaster`.`webmaster_id` = `webmaster_search_data`.`webmaster_id` 
WHERE 
    (full_name = '<name>') 
GROUP BY 
    `webmaster`.`dupe_master_id` 
LIMIT 50

添加 company_name(也已编入索引),查询时间飞速增长:

SELECT SQL_CALC_FOUND_ROWS 
    webmaster.dupe_master_id AS webmaster_id, 
    webmaster.first_name, 
    webmaster.family_name, 
    webmaster.job_title, 
    webmaster.company_name, 
    webmaster.address_1, 
    webmaster.address_2, 
    webmaster.town_city, 
    webmaster.state_county, 
    webmaster.post_code, 
    webmaster.email, 
    webmaster.ignored, 
    countries.country_name, 
    GROUP_CONCAT(DISTINCT titles.code ORDER BY code ASC) AS sub_string, 
    '' AS expo_string 
FROM 
    (`webmaster`) 
    LEFT JOIN `countries` ON `countries`.`country_id` = `webmaster`.`country_id` 
    LEFT JOIN `red_subscriptions` ON `red_subscriptions`.`webmaster_id` = `webmaster`.`webmaster_id` AND red_subscriptions.subscription_status_id = 2 
    LEFT JOIN `titles` ON `titles`.`title_id` = `red_subscriptions`.`title_id` 
    LEFT JOIN `webmaster_tags` ON `webmaster_tags`.`webmaster_id` = `webmaster`.`webmaster_id` 
    LEFT JOIN `tags` ON `tags`.`tag_id` = `webmaster_tags`.`tag_id` 
    INNER JOIN `webmaster_search_data` ON `webmaster`.`webmaster_id` = `webmaster_search_data`.`webmaster_id` 
WHERE 
    (full_name = '<name>' OR company_name '<name>') 
GROUP BY 
    `webmaster`.`dupe_master_id` 
LIMIT 50

仅解释全名:

id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE webmaster_search_data ref webmaster_id,full_name full_name 302 const 94 使用 where;使用临时的;使用文件排序 1 个简单的网站管理员 eq_ref PRIMARY PRIMARY 4 webmaster_search_data.webmaster_id 1 1 个简单国家 eq_ref PRIMARY PRIMARY 2 webmaster.country_id 1 1 SIMPLE red_subscriptions ref webmaster_id,subscription_status_id webmaster_id 4 webmaster_search_data.webmaster_id 1 1 个简单的标题 eq_ref PRIMARY PRIMARY 2 red_subscriptions.title_id 1 1 简单的 webmaster_tags 参考 webmaster_id webmaster_id 4 webmaster_search_data.webmaster_id 5 1 个简单标签 eq_ref PRIMARY PRIMARY 2 webmaster_tags.tag_id 1 使用索引

说明何时添加公司名称:

1 SIMPLE 网站管理员索引 PRIMARY,company_name dupe_master_id 4 NULL 2072015 使用文件排序 1 个简单国家 eq_ref PRIMARY PRIMARY 2 webmaster.country_id 1 1 SIMPLE red_subscriptions ref webmaster_id,subscription_status_id webmaster_id 4 webmaster.webmaster_id 1 1 个简单的标题 eq_ref PRIMARY PRIMARY 2 red_subscriptions.title_id 1 1 简单的 webmaster_tags 参考 webmaster_id webmaster_id 4 webmaster.webmaster_id 5 1 简单标签 eq_ref PRIMARY PRIMARY 2 webmaster_tags.tag_id 1 使用索引 1 SIMPLE webmaster_search_data eq_ref webmaster_id,full_name webmaster_id 4 webmaster.webmaster_id 1 使用位置

【问题讨论】:

EXPLAIN 对您的查询有何看法?请将此信息添加到您的问题中。 AND 和 OR 的组合是什么?也许第一个查询返回一百行,并用 OR 抛出一个公司名称返回一百万行?每个查询的预期计数是多少? 请为两个表和完整查询添加SHOW CREATE TABLE [tablename] 的输出,这将使回答更容易:) @Vyktor 我会在几分钟后发布完整的查询和解释。 添加示例查询和说明 【参考方案1】:

MySQL 不能同时使用两个索引。当您输入公司名称时,MySQL 不能再使用 Firstname、Lastname 上的索引,因为现在它必须检查更多列才能获得准确的结果。

它可能正在执行全表扫描。

您可以通过联合来拆分查询,这样您就可以将两列与索引一起使用。

SELECT * FROM
( SELECT * FROM table_a 
  INNER JOIN table_b ON table_a.ID = table_b.ID 
  WHERE table_a.first_name = 'Fred' 
  UNION
  SELECT * FROM table_a 
  INNER JOIN table_b ON table_a.ID = table_b.ID
  WHERE table_a.company_name = 'Widgets Inc'
) sub;

应单独评估每个查询并使用适当的索引。 UNION 会处理双打,所以你最终会得到相同的结果。

【讨论】:

抱歉延迟回复,但我会试一试。查询是在代码中生成的,因此正确地进行嵌套等可能会很有趣!

以上是关于使用两个索引列时缓慢的 mySQL 查询的主要内容,如果未能解决你的问题,请参考以下文章

了解为啥有大量文本列时按查询分组会变慢

缓慢的 MySQL 查询。我应该索引啥?

非常简单的 MySQL 索引查询运行非常缓慢

在 WHERE 子句中使用连接列时,Mysql 未在 LEFT JOIN 中使用索引

带有重复子查询的mySQL select联合查询工作缓慢

mysql查询缓慢原因和解决方案