帮助优化 MySQL 查询

Posted

技术标签:

【中文标题】帮助优化 MySQL 查询【英文标题】:Help optimizing MySQL query 【发布时间】:2009-09-19 15:27:26 【问题描述】:

(使用 mysql 4.1.22)

我无法让我的这个查询在大表(200k+ 行)上使用索引,它正在对其进行全表扫描。现在查询大约需要 1.2 秒。如果可能的话,我希望它小于 0.2 秒。

这是我的查询:

SELECT st_issues.issue_id, st_issues.cat_id,st_categories.name AS cat_name, st_issues.status_id,st_statuses.name AS status_name, st_issues.priority_id,st_priorities.name AS priority_name,st_priorities.color AS color, st_issues.assigned_cid,assigned_u.firstname,assigned_u.lastname,assigned_u.screenname, message, rating, created_by_email,created_by_cid,created_by_uid,by_user.firstname AS by_firstname,by_user.lastname AS by_lastname,by_user.screenname AS by_screenname, st_issues.browser,from_url,created_by_store,created,st_issues.stamp
FROM st_issues
 JOIN st_categories ON (st_issues.cat_id=st_categories.cat_id)
 JOIN st_statuses ON (st_issues.status_id=st_statuses.status_id)
 JOIN st_priorities ON (st_issues.priority_id=st_priorities.priority_id)
 LEFT JOIN users AS assigned_u ON (assigned_u.cid=st_issues.assigned_cid)
 LEFT JOIN users AS by_user ON (by_user.uid=st_issues.created_by_uid)
 LEFT JOIN st_issue_changes ON (st_issues.issue_id=st_issue_changes.issue_id AND change_id=0)
WHERE st_issues.assigned_cid=0

解释结果:

1, 'SIMPLE', 'st_issues', 'ALL', '', '', , '', 4, 'Using where'
1, 'SIMPLE', 'st_categories', 'eq_ref', 'PRIMARY', 'PRIMARY', 1, 'sg.st_issues.cat_id', 1, ''
1, 'SIMPLE', 'st_priorities', 'eq_ref', 'PRIMARY', 'PRIMARY', 1, 'sg.st_issues.priority_id', 1, ''
1, 'SIMPLE', 'assigned_u', 'ref', 'cid', 'cid', 8, 'sg.st_issues.assigned_cid', 1, ''
1, 'SIMPLE', 'st_statuses', 'ALL', 'PRIMARY', '', , '', 4, 'Using where'
1, 'SIMPLE', 'by_user', 'ALL', '', '', , '', 221623, ''
1, 'SIMPLE', 'st_issue_changes', 'eq_ref', 'PRIMARY', 'PRIMARY', 6, 'sg.st_issues.issue_id,const', 1, ''

显然问题在于“by_user”上的连接,因为它没有使用索引。

这里是'users'表的一些定义:

CREATE TABLE  `users` (
  `cid` double unsigned NOT NULL auto_increment,
  `uid` varchar(20) NOT NULL default '',
...
  `firstname` varchar(20) default NULL,
  `lastname` varchar(20) default NULL,
...
  PRIMARY KEY  (`uid`),
...
) ENGINE=InnoDB

有人知道为什么它没有在连接中使用主键吗? 有人对如何加快此查询的速度有任何想法或提示吗?

(如果需要/需要,我可以添加其他表的表定义)

编辑:

这是 st_issues 的表定义:

CREATE TABLE  `st_issues` (
  `issue_id` int(10) unsigned NOT NULL auto_increment,
  `cat_id` tinyint(3) unsigned NOT NULL default '0',
  `status_id` tinyint(3) unsigned NOT NULL default '0',
  `priority_id` tinyint(3) unsigned NOT NULL default '0',
  `assigned_cid` int(10) unsigned NOT NULL default '0',
  `rating` tinyint(4) default NULL,
  `created_by_email` varchar(255) NOT NULL default '',
  `created_by_cid` int(10) unsigned NOT NULL default '0',
  `created_by_uid` varchar(20) NOT NULL default '',
  `created_by_store` tinyint(3) unsigned NOT NULL default '0',
  `browser` varchar(255) NOT NULL default '',
  `from_url` varchar(255) NOT NULL default '',
  `created` datetime NOT NULL default '0000-00-00 00:00:00',
  `stamp` datetime NOT NULL default '0000-00-00 00:00:00',
  PRIMARY KEY  (`issue_id`),
  KEY `idx_create_by_cid` (`created_by_cid`),
  KEY `idx_create_by_uid` (`created_by_uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

【问题讨论】:

您在 st_issues 上有哪些索引? 你定义了哪些索引? 我刚刚添加了st_issues的定义 【参考方案1】:

这就是 users 表的全部定义吗?

因为它说:

) 引擎=InnoDB

而 st_issues 说:

) ENGINE=InnoDB 默认字符集=utf8;

如果您的两个表使用不同的排序规则,则 uid 和 created_by_uid 的两个字符串数据类型是不同的,并且 MySQL 在比较它们之前必须执行字符集强制,从而破坏您的索引。

最好确保对数据库中的所有文本使用相同的字符集/排序规则。

【讨论】:

【参考方案2】:

我做了一些测试,发现以下更改有所帮助:

st_issues.assigned_cid上添加索引。

users表的主键更改为cid而不是uid

by_user 的连接条件更改为使用cid 而不是uid

LEFT JOIN users AS by_user ON (by_user.cid=st_issues.created_by_cid)

然后我得到了以下EXPLAIN 报告(尽管数据行为零):

+----+-------------+------------------+--------+---------------+--------------+---------+-------------------------------+------+-------------+
| id | select_type | table            | type   | possible_keys | key          | key_len | ref                           | rows | Extra       |
+----+-------------+------------------+--------+---------------+--------------+---------+-------------------------------+------+-------------+
|  1 | SIMPLE      | st_issues        | ref    | assigned_cid  | assigned_cid | 4       | const                         |    1 |             | 
|  1 | SIMPLE      | st_categories    | eq_ref | PRIMARY       | PRIMARY      | 1       | test.st_issues.cat_id         |    1 |             | 
|  1 | SIMPLE      | st_statuses      | eq_ref | PRIMARY       | PRIMARY      | 1       | test.st_issues.status_id      |    1 |             | 
|  1 | SIMPLE      | st_priorities    | eq_ref | PRIMARY       | PRIMARY      | 1       | test.st_issues.priority_id    |    1 |             | 
|  1 | SIMPLE      | assigned_u       | eq_ref | PRIMARY       | PRIMARY      | 8       | test.st_issues.assigned_cid   |    1 |             | 
|  1 | SIMPLE      | by_user          | eq_ref | PRIMARY       | PRIMARY      | 8       | test.st_issues.created_by_cid |    1 |             | 
|  1 | SIMPLE      | st_issue_changes | eq_ref | PRIMARY       | PRIMARY      | 8       | test.st_issues.issue_id,const |    1 | Using index | 
+----+-------------+------------------+--------+---------------+--------------+---------+-------------------------------+------+-------------+

这表明优化器已为每个表选择了一个索引,而在您的查询版本中并没有。我不得不猜测您的查找表的定义。

我建议的另一件事是使用自然键(类别或状态的名称)定义您的查找表st_categoriesst_statuses。然后从st_issues 表中引用该自然键,而不是使用tinyint 伪键。优点是您不必执行这些连接来获取类别或状态的名称;它已经在st_issues 表中。

【讨论】:

以上是关于帮助优化 MySQL 查询的主要内容,如果未能解决你的问题,请参考以下文章

需要帮助优化 mysql 查询以使其按索引快速排序

使用临时的 MYSQL 优化;使用文件排序!!! -> 帮助

Mysql优化之慢查询优化

帮助优化查询(显示联系人之间双向关系的强度)

需要查询修复和优化帮助(查找每周股票数据的最高价、最高价和最后已知的收盘价)

Explain 执行计划 和 SQL优化