为简单的三表模式优化MySQL LEFT JOIN查询?

时间:2019-01-17 17:03:03

标签: mysql join query-optimization

我已经用PHP编写了一个带有MySQL数据库的求职系统,但查询缓慢却遇到了问题。

我的模式(简化)如下:

tbl_job
job_id
job_desc requester_user_id

tbl_user
user_id
user_name

tbl_workermap
workermap_id
job_id
worker_user_id

一个包含作业的表,一个可能的工作人员的用户表以及一个将工作人员映射到工作的表。一个工作可以有一个或多个工人,一个工人可以有一个或多个职位。

tbl_user同时包含请求工作的用户和从事工作的用户,因此用户ID存储在tbl_workermap的worker_user_id和tbl_job的requester_user_id下

记录作业后,它将在tbl_job中创建一个条目,但在tbl_workermap中将不创建任何条目,除非有人专门分配了一个工人。这意味着当我查询作业时,我会使用左联接来执行此操作,因为tbl_workermap中没有每个作业的条目:

SELECT 
job.job_id,
job.job_desc,
workermap.worker_user_id,
worker.worker_name

FROM tbl_job AS job

LEFT JOIN tbl_workermap AS workermap
ON job.job_id = workermap.job_id

LEFT JOIN tbl_user AS worker
ON workermap.worker_user_id = worker.user_id

该系统已经使用了一段时间,我现在在tbl_job中有大约8000个条目,在tbl_workermap中有7000个条目,检索所有结果要花费4秒钟以上。一个EXPLAIN查询显示tbl_workermap联接返回大约7000行,并显示“使用位置;使用联接缓冲区(块嵌套循环)”。

我能做些什么来加快速度吗?

编辑:添加表格信息
我已经简化了一些事情来解释,但这是实际的表结构。联接更多,但是tbl_workermap是唯一有问题的联接:

CREATE TABLE `tbl_job` (
  `job_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `job_title` varchar(100) DEFAULT NULL,
  `job_description` text,
  `job_added_datetime` int(11) DEFAULT '0',
  `job_due_datetime` int(11) NOT NULL DEFAULT '0',
  `job_time_estimate` int(11) DEFAULT NULL,
  `job_additional_fields` text,
  `addedby_user_id` int(11) NOT NULL DEFAULT '0',
  `requester_user_id` int(11) NOT NULL DEFAULT '0',
  `worker_user_id` int(11) NOT NULL DEFAULT '0',
  `job_active` tinyint(4) NOT NULL DEFAULT '1',
  `site_id` tinyint(4) NOT NULL DEFAULT '1',
  `status_id` int(11) NOT NULL DEFAULT '1',
  `estimategroup_id` int(11) DEFAULT '1',
  `brand_id` int(11) DEFAULT '1',
  `job_isproject` tinyint(4) DEFAULT NULL,
  PRIMARY KEY (`job_id`),
  FULLTEXT KEY `job_title` (`job_title`,`job_description`,`job_additional_fields`)
) ENGINE=MyISAM AUTO_INCREMENT=8285 DEFAULT CHARSET=latin1



CREATE TABLE `tbl_user` (
  `user_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user_shortname` varchar(30) DEFAULT NULL,
  `user_name` varchar(30) DEFAULT NULL,
  `user_password` varchar(50) DEFAULT NULL,
  `user_password_reset_uuid` varchar(50) DEFAULT NULL,
  `user_email` varchar(50) DEFAULT NULL,
  `user_description` text,
  `user_sortorder` int(11) NOT NULL DEFAULT '0',
  `user_isworker` tinyint(4) NOT NULL DEFAULT '0',
  `user_active` tinyint(4) NOT NULL DEFAULT '1',
  `site_id` tinyint(4) NOT NULL DEFAULT '0',
  `user_avatar_file_id` int(11) DEFAULT NULL,
  `user_avatar_hub_url` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=MyISAM AUTO_INCREMENT=917 DEFAULT CHARSET=latin1


CREATE TABLE `tbl_workermap` (
  `workermap_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `job_id` int(11) DEFAULT NULL,
  `workermap_datetime_added` int(11) DEFAULT NULL,
  `workermap_datetime_removed` int(11) DEFAULT NULL,
  `worker_user_id` int(11) DEFAULT NULL,
  `addedby_user_id` int(11) DEFAULT NULL,
  `removedby_user_id` int(11) DEFAULT NULL,
  `site_id` int(11) DEFAULT NULL,
  `workermap_isassigned` int(11) DEFAULT NULL,
  `workermap_active` int(11) NOT NULL DEFAULT '1',
  PRIMARY KEY (`workermap_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7145 DEFAULT CHARSET=latin1

SHOW INDEX

+---------+---+-----------+---+-----------------------+------+------+------+------+-----+----------+--+--+
| tbl_job | 0 |  PRIMARY  | 1 |        job_id         |  A   | 8283 | NULL | NULL |     |  BTREE   |  |  |
+---------+---+-----------+---+-----------------------+------+------+------+------+-----+----------+--+--+
| tbl_job | 1 | job_title | 1 | job_title             | NULL |    1 | NULL | NULL | YES | FULLTEXT |  |  |
| tbl_job | 1 | job_title | 2 | job_description       | NULL |    1 | NULL | NULL | YES | FULLTEXT |  |  |
| tbl_job | 1 | job_title | 3 | job_additional_fields | NULL |    1 | NULL | NULL | YES | FULLTEXT |  |  |
+---------+---+-----------+---+-----------------------+------+------+------+------+-----+----------+--+--+

+----------+---+---------+---+---------+---+-----+------+------+--+-------+--+--+
| tbl_user | 0 | PRIMARY | 1 | user_id | A | 910 | NULL | NULL |  | BTREE |  |  |
+----------+---+---------+---+---------+---+-----+------+------+--+-------+--+--+

+---------------+---+---------+---+--------------+---+------+------+------+--+-------+--+--+
| tbl_workermap | 0 | PRIMARY | 1 | workermap_id | A | 7184 | NULL | NULL |  | BTREE |  |  |
+---------------+---+---------+---+--------------+---+------+------+------+--+-------+--+--+

EXPLAIN查询

+---+--------+----------------+--------+---------+---------+------+-------------------------------+------+----------------------------------------------------+
| 1 | SIMPLE |      job       |  ALL   |  NULL   |  NULL   | NULL |             NULL              | 8283 |    Using where; Using temporary; Using filesort    |
+---+--------+----------------+--------+---------+---------+------+-------------------------------+------+----------------------------------------------------+
| 1 | SIMPLE | estimategroup  | eq_ref | PRIMARY | PRIMARY | 4    | jobq.job.estimategroup_id     |    1 | Using where                                        |
| 1 | SIMPLE | brand          | eq_ref | PRIMARY | PRIMARY | 4    | jobq.job.brand_id             |    1 | Using index condition                              |
| 1 | SIMPLE | site           | eq_ref | PRIMARY | PRIMARY | 4    | jobq.job.site_id              |    1 | Using where                                        |
| 1 | SIMPLE | addedby        | eq_ref | PRIMARY | PRIMARY | 4    | jobq.job.addedby_user_id      |    1 | Using index condition                              |
| 1 | SIMPLE | requester      | eq_ref | PRIMARY | PRIMARY | 4    | jobq.job.requester_user_id    |    1 | Using index condition                              |
| 1 | SIMPLE | worker         | eq_ref | PRIMARY | PRIMARY | 4    | jobq.job.worker_user_id       |    1 | Using index condition                              |
| 1 | SIMPLE | status         | ALL    | PRIMARY | NULL    | NULL | NULL                          |    6 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | workermap      | ALL    | NULL    | NULL    | NULL | NULL                          | 7184 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | user_workermap | eq_ref | PRIMARY | PRIMARY | 4    | jobq.workermap.worker_user_id |    1 | Using where                                        |
| 1 | SIMPLE | categorymap    | ALL    | NULL    | NULL    | NULL | NULL                          |    1 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | category       | eq_ref | PRIMARY | PRIMARY | 4    | jobq.categorymap.category_id  |    1 | Using where                                        |
+---+--------+----------------+--------+---------+---------+------+-------------------------------+------+----------------------------------------------------+

3 个答案:

答案 0 :(得分:1)

2019年1月18日13:43当前,您需要两个索引来涵盖JOIN的左右两个对象的基本规则=需要一个索引。 1.创建后更改表tbl_workermap添加索引idx_t_w_map_job_id(job_id)2.创建后更改表tbl_workermap添加索引IDx_t_w_map_wrk_user_id(worker_user_id)运行EXPLAIN .....以查看新的执行计划。

答案 1 :(得分:0)

如果尚未创建聚集索引,请使用setting a primary key(假设表已正确规范化)。 (如果没有,那么您可能也想setup foreign key constraints。)

如果此问题涉及Microsoft SQL Server ,我建议创建一个存储过程,尤其是当该查询作为某种常规过程频繁运行时。正如this answer中所述,然而,像这样的简单查询的主要性能优势将来自表设计和索引。

答案 2 :(得分:0)

“一项工作可以有一个或多个工人。”反之亦然(一个工人可以从事多个工作)?如果没有,那么您只有1:1,并且不应使用该额外的表来实现它。

假设确实有很多:很多,here有一些优化该表的技巧。

除非期望“正确的”表缺少所需的行,否则不要使用LEFT

样式提示:摆脱tbl_user_user_id除外)等。这就是名称的前缀在上下文中是混乱且多余的。在“用户”与“工人”之间保持一致。

具有用两个目标命名的many:many表(例如worker_job)。但是,我现在看到它不仅仅是一个简单的many:many映射表;它更像是一个表,用于分配和跟踪谁在一段时间内正在做什么??

如果您同时需要谁在从事什么工作的历史,并且 谁在从事什么工作的当前状态,请考虑使用两张桌子。历史在不断发展。 “当前”不断变化。

使用合适的数据类型,例如DATEDATETIME

使用InnoDB代替MyISAM。