使用 5 个左连接提高 MySQL 查询的速度
Improve speed of MySQL query with 5 left joins
正在开发支持票务系统,但票数不多(~3,000)。要获取票证信息的摘要网格,自定义字段 table (j25_field_value) 上有五个 LEFT JOIN 语句,包含大约 10,000 条记录。查询 运行 太长(~10 秒)并且在带有 WHERE 子句的情况下,它 运行 甚至更长(最多 ~30 秒或更多)。
关于改进查询以将时间减少到 运行 的任何建议?
四个table:
j25_support_tickets
CREATE TABLE `j25_support_tickets` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`category_id` int(11) NOT NULL DEFAULT '0',
`user_id` int(11) DEFAULT NULL,
`email` varchar(50) DEFAULT NULL,
`subject` varchar(255) DEFAULT NULL,
`message` text,
`modified_date` datetime DEFAULT NULL,
`priority_id` tinyint(3) unsigned DEFAULT NULL,
`status_id` tinyint(3) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3868 DEFAULT CHARSET=utf8
j25_support_priorities
CREATE TABLE `j25_support_priorities` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=14 DEFAULT CHARSET=utf8
j25_support_statuses
CREATE TABLE `j25_support_statuses` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=7 DEFAULT CHARSET=utf8
j25_field_value (id, ticket_id, field_id, field_value)
CREATE TABLE `j25_support_field_value` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ticket_id` int(11) DEFAULT NULL,
`field_id` int(11) DEFAULT NULL,
`field_value` tinytext,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=10889 DEFAULT CHARSET=utf8
还有,运行这个:
SELECT LENGTH(field_value) len FROM j25_support_field_value ORDER BY len DESC LIMIT 1
note: the result = 38
查询:
SELECT DISTINCT t.id as ID
, (select p.title from j25_support_priorities p where p.id = t.priority_id) as Priority
, (select s.title from j25_support_statuses s where s.id = t.status_id) as Status
, t.subject as Subject
, t.email as SubmittedByEmail
, type.field_value AS IssueType
, ver.field_value AS Version
, utype.field_value AS UserType
, cust.field_value AS Company
, refno.field_value AS RefNo
, t.modified_date as Modified
FROM j25_support_tickets AS t
LEFT JOIN j25_support_field_value AS type ON t.id = type.ticket_id AND type.field_id =1
LEFT JOIN j25_support_field_value AS ver ON t.id = ver.ticket_id AND ver.field_id =2
LEFT JOIN j25_support_field_value AS utype ON t.id = utype.ticket_id AND utype.field_id =3
LEFT JOIN j25_support_field_value AS cust ON t.id = cust.ticket_id AND cust.field_id =4
LEFT JOIN j25_support_field_value AS refno ON t.id = refno.ticket_id AND refno.field_id =5
您可以取消 starters 的子查询,只从另一个连接中获取它们。您可以添加索引 j25_support_field_value
alter table j25_support_field_value add key(id, field_type);
我假设 j25_support_tickets 中的 id 有一个索引 - 如果没有并且它们是唯一的,请添加一个唯一索引 alter table j25_support_tickets add unique key(id);
如果它们不是唯一的,请从中删除唯一这个词声明。
在 MySQL 中,联接通常需要您用于联接的字段的索引。这将适用于巨大的表(100m+)并产生非常合理的结果,如果你遵循这个规则,你就不会出错。
j25_support_tickets 中的 ID 是否唯一?如果他们是你可以取消不同 - 如果不是,或者如果你在每一行中得到精确的重复,仍然取消不同并添加一个 t.id 到这个末尾的组:
SELECT t.id as ID
, p.title as Priority
, s.title as Status
, t.subject as Subject
, t.email as SubmittedByEmail
, type.field_value AS IssueType
, ver.field_value AS Version
, utype.field_value AS UserType
, cust.field_value AS Company
, refno.field_value AS RefNo
, t.modified_date as Modified
FROM j25_support_tickets AS t
LEFT JOIN j25_support_field_value AS type ON t.id = type.ticket_id AND type.field_id =1
LEFT JOIN j25_support_field_value AS ver ON t.id = ver.ticket_id AND ver.field_id =2
LEFT JOIN j25_support_field_value AS utype ON t.id = utype.ticket_id AND utype.field_id =3
LEFT JOIN j25_support_field_value AS cust ON t.id = cust.ticket_id AND cust.field_id =4
LEFT JOIN j25_support_field_value AS refno ON t.id = refno.ticket_id AND refno.field_id =5
LEFT JOIN j25_support_priorities p ON p.id = t.priority_id
LEFT JOIN j25_support_statuses s ON s.id = t.status_id;
ALTER TABLE j25_support_field_value
ADD INDEX (`ticket_id`,`field_id`,`field_value`(50))
该索引将作为您查询的覆盖索引。它将允许连接仅使用该索引来查找值。它的执行速度应该比没有此索引快得多,因为目前您的查询必须读取 table 中的每一行才能找到与 ticket_id
和 field_id
.[=14= 的每个组合相匹配的内容]
我还建议将您的 table 转换为 InnoDB 引擎,除非您有非常明确的理由使用 MyISAM。
ALTER TABLE tablename ENGINE=InnoDB
同上 - 更好的索引会有所帮助。然后,您可能也可以将查询简化为类似的内容(仅加入 table 一次):
SELECT t.id as ID
, p.title as Priority
, s.title as Status
, t.subject as Subject
, t.email as SubmittedByEmail
, case when v.field_id=1 then v.field_value else null end as IssueType
, case when v.field_id=2 then v.field_value else null end as Version
, case when v.field_id=3 then v.field_value else null end as UserType
, case when v.field_id=4 then v.field_value else null end as Company
, case when v.field_id=5 then v.field_value else null end as RefNo
, t.modified_date as Modified
FROM j25_support_tickets AS t
LEFT JOIN j25_support_field_value v ON t.id = v.ticket_id
LEFT JOIN j25_support_priorities p ON p.id = t.priority_id
LEFT JOIN j25_support_statuses s ON s.id = t.status_id;
- 切换到 InnoDB。
- 切换到 InnoDB 后,使
j25_support_field_value
的 PRIMARY KEY
为 (ticket_id, field_id)
(如果 id
则删除) . (攻击 field_value(50)
会很痛, 不会 有帮助。)
- 一个
PRIMARY KEY
是一个 UNIQUE KEY
,所以不要两者都有。
- 使用
VARCHAR(255)
而不是几乎等价的 TINYTEXT
。
- EAV 架构糟透了。 My ran on EAV.
正在开发支持票务系统,但票数不多(~3,000)。要获取票证信息的摘要网格,自定义字段 table (j25_field_value) 上有五个 LEFT JOIN 语句,包含大约 10,000 条记录。查询 运行 太长(~10 秒)并且在带有 WHERE 子句的情况下,它 运行 甚至更长(最多 ~30 秒或更多)。
关于改进查询以将时间减少到 运行 的任何建议?
四个table:
j25_support_tickets
CREATE TABLE `j25_support_tickets` ( `id` int(11) NOT NULL AUTO_INCREMENT, `category_id` int(11) NOT NULL DEFAULT '0', `user_id` int(11) DEFAULT NULL, `email` varchar(50) DEFAULT NULL, `subject` varchar(255) DEFAULT NULL, `message` text, `modified_date` datetime DEFAULT NULL, `priority_id` tinyint(3) unsigned DEFAULT NULL, `status_id` tinyint(3) unsigned DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=3868 DEFAULT CHARSET=utf8
j25_support_priorities
CREATE TABLE `j25_support_priorities` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=14 DEFAULT CHARSET=utf8
j25_support_statuses
CREATE TABLE `j25_support_statuses` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=7 DEFAULT CHARSET=utf8
j25_field_value (id, ticket_id, field_id, field_value)
CREATE TABLE `j25_support_field_value` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ticket_id` int(11) DEFAULT NULL, `field_id` int(11) DEFAULT NULL, `field_value` tinytext, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=10889 DEFAULT CHARSET=utf8
还有,运行这个:
SELECT LENGTH(field_value) len FROM j25_support_field_value ORDER BY len DESC LIMIT 1
note: the result = 38
查询:
SELECT DISTINCT t.id as ID
, (select p.title from j25_support_priorities p where p.id = t.priority_id) as Priority
, (select s.title from j25_support_statuses s where s.id = t.status_id) as Status
, t.subject as Subject
, t.email as SubmittedByEmail
, type.field_value AS IssueType
, ver.field_value AS Version
, utype.field_value AS UserType
, cust.field_value AS Company
, refno.field_value AS RefNo
, t.modified_date as Modified
FROM j25_support_tickets AS t
LEFT JOIN j25_support_field_value AS type ON t.id = type.ticket_id AND type.field_id =1
LEFT JOIN j25_support_field_value AS ver ON t.id = ver.ticket_id AND ver.field_id =2
LEFT JOIN j25_support_field_value AS utype ON t.id = utype.ticket_id AND utype.field_id =3
LEFT JOIN j25_support_field_value AS cust ON t.id = cust.ticket_id AND cust.field_id =4
LEFT JOIN j25_support_field_value AS refno ON t.id = refno.ticket_id AND refno.field_id =5
您可以取消 starters 的子查询,只从另一个连接中获取它们。您可以添加索引 j25_support_field_value
alter table j25_support_field_value add key(id, field_type);
我假设 j25_support_tickets 中的 id 有一个索引 - 如果没有并且它们是唯一的,请添加一个唯一索引 alter table j25_support_tickets add unique key(id);
如果它们不是唯一的,请从中删除唯一这个词声明。
在 MySQL 中,联接通常需要您用于联接的字段的索引。这将适用于巨大的表(100m+)并产生非常合理的结果,如果你遵循这个规则,你就不会出错。
j25_support_tickets 中的 ID 是否唯一?如果他们是你可以取消不同 - 如果不是,或者如果你在每一行中得到精确的重复,仍然取消不同并添加一个 t.id 到这个末尾的组:
SELECT t.id as ID
, p.title as Priority
, s.title as Status
, t.subject as Subject
, t.email as SubmittedByEmail
, type.field_value AS IssueType
, ver.field_value AS Version
, utype.field_value AS UserType
, cust.field_value AS Company
, refno.field_value AS RefNo
, t.modified_date as Modified
FROM j25_support_tickets AS t
LEFT JOIN j25_support_field_value AS type ON t.id = type.ticket_id AND type.field_id =1
LEFT JOIN j25_support_field_value AS ver ON t.id = ver.ticket_id AND ver.field_id =2
LEFT JOIN j25_support_field_value AS utype ON t.id = utype.ticket_id AND utype.field_id =3
LEFT JOIN j25_support_field_value AS cust ON t.id = cust.ticket_id AND cust.field_id =4
LEFT JOIN j25_support_field_value AS refno ON t.id = refno.ticket_id AND refno.field_id =5
LEFT JOIN j25_support_priorities p ON p.id = t.priority_id
LEFT JOIN j25_support_statuses s ON s.id = t.status_id;
ALTER TABLE j25_support_field_value
ADD INDEX (`ticket_id`,`field_id`,`field_value`(50))
该索引将作为您查询的覆盖索引。它将允许连接仅使用该索引来查找值。它的执行速度应该比没有此索引快得多,因为目前您的查询必须读取 table 中的每一行才能找到与 ticket_id
和 field_id
.[=14= 的每个组合相匹配的内容]
我还建议将您的 table 转换为 InnoDB 引擎,除非您有非常明确的理由使用 MyISAM。
ALTER TABLE tablename ENGINE=InnoDB
同上 - 更好的索引会有所帮助。然后,您可能也可以将查询简化为类似的内容(仅加入 table 一次):
SELECT t.id as ID
, p.title as Priority
, s.title as Status
, t.subject as Subject
, t.email as SubmittedByEmail
, case when v.field_id=1 then v.field_value else null end as IssueType
, case when v.field_id=2 then v.field_value else null end as Version
, case when v.field_id=3 then v.field_value else null end as UserType
, case when v.field_id=4 then v.field_value else null end as Company
, case when v.field_id=5 then v.field_value else null end as RefNo
, t.modified_date as Modified
FROM j25_support_tickets AS t
LEFT JOIN j25_support_field_value v ON t.id = v.ticket_id
LEFT JOIN j25_support_priorities p ON p.id = t.priority_id
LEFT JOIN j25_support_statuses s ON s.id = t.status_id;
- 切换到 InnoDB。
- 切换到 InnoDB 后,使
j25_support_field_value
的PRIMARY KEY
为(ticket_id, field_id)
(如果id
则删除) . (攻击field_value(50)
会很痛, 不会 有帮助。) - 一个
PRIMARY KEY
是一个UNIQUE KEY
,所以不要两者都有。 - 使用
VARCHAR(255)
而不是几乎等价的TINYTEXT
。 - EAV 架构糟透了。 My ran on EAV.