【问题标题】:MySQL Query performance - huge difference in timeMySQL 查询性能 - 时间上的巨大差异
【发布时间】:2011-02-02 16:45:49
【问题描述】:

我有一个查询在 2 个数据集之间返回的时间相差很大。对于一组(数据库 A),它会在几秒钟内返回,对于另一组(数据库 B)......好吧,我还没有等待足够长的时间,但是超过 10 分钟。我已将这两个数据库转储到我的本地计算机上,我可以在其中重现运行 MySQL 5.1.37 的问题。

奇怪的是,数据库 B 比数据库 A 小。

重现问题的精简版查询是:

SELECT * FROM po_shipment ps 
JOIN po_shipment_item psi USING (ship_id) 
JOIN po_alloc pa ON ps.ship_id = pa.ship_id AND pa.UID_items = psi.UID_items 
JOIN po_header ph ON pa.hdr_id = ph.hdr_id 
LEFT JOIN EVENT_TABLE ev0 ON ev0.TABLE_ID1 = ps.ship_id AND ev0.EVENT_TYPE = 'MAS0' 
LEFT JOIN EVENT_TABLE ev1 ON ev1.TABLE_ID1 = ps.ship_id AND ev1.EVENT_TYPE = 'MAS1' 
LEFT JOIN EVENT_TABLE ev2 ON ev2.TABLE_ID1 = ps.ship_id AND ev2.EVENT_TYPE = 'MAS2' 
LEFT JOIN EVENT_TABLE ev3 ON ev3.TABLE_ID1 = ps.ship_id AND ev3.EVENT_TYPE = 'MAS3' 
LEFT JOIN EVENT_TABLE ev4 ON ev4.TABLE_ID1 = ps.ship_id AND ev4.EVENT_TYPE = 'MAS4' 
LEFT JOIN EVENT_TABLE ev5 ON ev5.TABLE_ID1 = ps.ship_id AND ev5.EVENT_TYPE = 'MAS5' 
WHERE ps.eta >= '2010-03-22'
GROUP BY ps.ship_id
LIMIT 100;

在约 2 秒内返回的第一个数据库 (A) 的 EXPLAIN 查询计划是:

+----+-------------+-------+--------+----------------------------------------------------------------------------------------------------------------------------------------+----------------------------------+---------+------------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                                                                                                                          | key                              | key_len | ref                          | rows | Extra                                        |
+----+-------------+-------+--------+----------------------------------------------------------------------------------------------------------------------------------------+----------------------------------+---------+------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | ps    | range  | PRIMARY,IX_ETA_DATE                                                                                                                    | IX_ETA_DATE                      | 4       | NULL                         |  174 | Using where; Using temporary; Using filesort | 
|  1 | SIMPLE      | ev0   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev1   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev2   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev3   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev4   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev5   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | psi   | ref    | PRIMARY,IX_po_shipment_item_po_shipment1,FK_po_shipment_item_po_shipment1                                                              | IX_po_shipment_item_po_shipment1 | 4       | UNIVIS_PROD.ps.ship_id       |    1 |                                              | 
|  1 | SIMPLE      | pa    | ref    | IX_po_alloc_po_shipment_item2,IX_po_alloc_po_details_old,FK_po_alloc_po_shipment1,FK_po_alloc_po_shipment_item1,FK_po_alloc_po_header1 | FK_po_alloc_po_shipment1         | 4       | UNIVIS_PROD.psi.ship_id      |    5 | Using where                                  | 
|  1 | SIMPLE      | ph    | eq_ref | PRIMARY,IX_HDR_ID                                                                                                                      | PRIMARY                          | 4       | UNIVIS_PROD.pa.hdr_id        |    1 |                                              | 
+----+-------------+-------+--------+----------------------------------------------------------------------------------------------------------------------------------------+----------------------------------+---------+------------------------------+------+----------------------------------------------+

在 >600 秒内返回的第二个数据库 (B) 的 EXPLAIN 查询计划是:

+----+-------------+-------+--------+----------------------------------------------------------------------------------------------------------------------------------------+----------------------------------+---------+--------------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                                                                                                                          | key                              | key_len | ref                            | rows | Extra                                        |
+----+-------------+-------+--------+----------------------------------------------------------------------------------------------------------------------------------------+----------------------------------+---------+--------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | ps    | range  | PRIMARY,IX_ETA_DATE                                                                                                                    | IX_ETA_DATE                      | 4       | NULL                           |   38 | Using where; Using temporary; Using filesort | 
|  1 | SIMPLE      | psi   | ref    | PRIMARY,IX_po_shipment_item_po_shipment1,FK_po_shipment_item_po_shipment1                                                              | IX_po_shipment_item_po_shipment1 | 4       | UNIVIS_DEV01.ps.ship_id        |    1 |                                              | 
|  1 | SIMPLE      | ev0   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_DEV01.psi.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev1   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_DEV01.psi.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev2   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_DEV01.ps.ship_id,const  |    1 |                                              | 
|  1 | SIMPLE      | ev3   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_DEV01.psi.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev4   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_DEV01.psi.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev5   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_DEV01.ps.ship_id,const  |    1 |                                              | 
|  1 | SIMPLE      | pa    | ref    | IX_po_alloc_po_shipment_item2,IX_po_alloc_po_details_old,FK_po_alloc_po_shipment1,FK_po_alloc_po_shipment_item1,FK_po_alloc_po_header1 | IX_po_alloc_po_shipment_item2    | 4       | UNIVIS_DEV01.ps.ship_id        |    4 | Using where                                  | 
|  1 | SIMPLE      | ph    | eq_ref | PRIMARY,IX_HDR_ID                                                                                                                      | PRIMARY                          | 4       | UNIVIS_DEV01.pa.hdr_id         |    1 |                                              | 
+----+-------------+-------+--------+----------------------------------------------------------------------------------------------------------------------------------------+----------------------------------+---------+--------------------------------+------+----------------------------------------------+

当数据库 B 运行时,我可以查看 MySQL 管理员,并且状态无限期地保持在“正在复制到 tmp 表”。数据库 A 也有这种状态,但只有一秒钟左右。

这些数据库之间的表结构、索引、键等没有区别(我已经展示了创建表并对其进行了比较)。

桌子的大小是:

database A:
po_shipment 1776
po_shipment_item 1945
po_alloc 36298
po_header 71642
EVENT_TABLE 1608

database B:
po_shipment 463
po_shipment_item 470
po_alloc 3291
po_header 56149
EVENT_TABLE 1089

需要注意的几点:

  • 删除 WHERE 子句会使 查询返回
  • 删除 GROUP BY 会进行查询 返回
  • 删除 ev5、ev4、ev3 等会使 每个查询都变得更快 已删除。

UPDATE 在 AJ 的回答之后: - 数据库 B(最大值 = 800002752)上的 ship_id 的大小明显大于数据库 A(最大值 = 3489)。鉴于这些是 InnoDB 表,是否会更改任何缓冲区以帮助处理这种大小的键? 进一步更新:我减小了键的大小并重新分析,但性能仍然没有变化。

UPDATE EVENT_TABLE 的描述:

请注意,它在两个数据库中相同

+--------------------+--------------+------+-----+---------+----------------+
| Field              | Type         | Null | Key | Default | Extra          |
+--------------------+--------------+------+-----+---------+----------------+
| EVENT_TABLE_ID     | bigint(20)   | NO   | PRI | NULL    | auto_increment | 
| EVENT_TYPE         | varchar(10)  | NO   |     | NULL    |                | 
| TABLE_ID1          | int(11)      | NO   | MUL | NULL    |                | 
| TABLE_ID2          | int(11)      | YES  |     | NULL    |                | 
| TABLE_ID3          | int(11)      | YES  |     | NULL    |                | 
| TABLE_ID4          | int(11)      | YES  |     | NULL    |                | 
| EVENT_CREATED_DATE | datetime     | NO   |     | NULL    |                | 
| MESSAGE_REF        | varchar(100) | YES  |     | NULL    |                | 
+--------------------+--------------+------+-----+---------+----------------+

为了更好地衡量 SHOW CREATE TABLE EVENT_TABLE:

数据库之间唯一不同的是自动增量值

| EVENT_TABLE | CREATE TABLE `EVENT_TABLE` (
  `EVENT_TABLE_ID` bigint(20) NOT NULL AUTO_INCREMENT,
  `EVENT_TYPE` varchar(10) NOT NULL,
  `TABLE_ID1` int(11) NOT NULL,
  `TABLE_ID2` int(11) DEFAULT NULL,
  `TABLE_ID3` int(11) DEFAULT NULL,
  `TABLE_ID4` int(11) DEFAULT NULL,
  `EVENT_CREATED_DATE` datetime NOT NULL,
  `MESSAGE_REF` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`EVENT_TABLE_ID`),
  KEY `IX_EVENT_ID_EVENT_TYPE` (`TABLE_ID1`,`EVENT_TYPE`)
) ENGINE=InnoDB AUTO_INCREMENT=1925 DEFAULT CHARSET=utf8 | 

谁能建议如何解决这个问题?我错过了什么?

Michael Holzmann 提问后更新 这是基于他更新的 STRAIGHT_JOIN 查询的新查询计划。请注意,数据库 B 具有“使用临时;使用文件排序”,而现在数据库 A 没有。这可能是由于长键或类似原因造成的吗?

数据库A

+----+-------------+-------+--------+----------------------------------------------------------------------------------------------------------------------------------------+----------------------------------+---------+------------------------------+------+-------------+
| id | select_type | table | type   | possible_keys                                                                                                                          | key                              | key_len | ref                          | rows | Extra       |
+----+-------------+-------+--------+----------------------------------------------------------------------------------------------------------------------------------------+----------------------------------+---------+------------------------------+------+-------------+
|  1 | SIMPLE      | ps    | index  | PRIMARY,IX_ETA_DATE                                                                                                                    | PRIMARY                          | 4       | NULL                         |  168 | Using where | 
|  1 | SIMPLE      | ev0   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |             | 
|  1 | SIMPLE      | ev1   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |             | 
|  1 | SIMPLE      | ev2   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |             | 
|  1 | SIMPLE      | ev3   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |             | 
|  1 | SIMPLE      | ev4   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |             | 
|  1 | SIMPLE      | ev5   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |             | 
|  1 | SIMPLE      | psi   | ref    | PRIMARY,IX_po_shipment_item_po_shipment1,FK_po_shipment_item_po_shipment1                                                              | IX_po_shipment_item_po_shipment1 | 4       | UNIVIS_PROD.ps.ship_id       |    1 |             | 
|  1 | SIMPLE      | pa    | ref    | IX_po_alloc_po_shipment_item2,IX_po_alloc_po_details_old,FK_po_alloc_po_shipment1,FK_po_alloc_po_shipment_item1,FK_po_alloc_po_header1 | FK_po_alloc_po_shipment_item1    | 8       | UNIVIS_PROD.psi.UID_items    |    6 | Using where | 
|  1 | SIMPLE      | ph    | eq_ref | PRIMARY,IX_HDR_ID                                                                                                                      | PRIMARY                          | 4       | UNIVIS_PROD.pa.hdr_id        |    1 |             | 
+----+-------------+-------+--------+----------------------------------------------------------------------------------------------------------------------------------------+----------------------------------+---------+------------------------------+------+-------------+

数据库B

+----+-------------+-------+--------+----------------------------------------------------------------------------------------------------------------------------------------+----------------------------------+---------+-------------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                                                                                                                          | key                              | key_len | ref                           | rows | Extra                                        |
+----+-------------+-------+--------+----------------------------------------------------------------------------------------------------------------------------------------+----------------------------------+---------+-------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | ps    | range  | PRIMARY,IX_ETA_DATE                                                                                                                    | IX_ETA_DATE                      | 4       | NULL                          |   38 | Using where; Using temporary; Using filesort | 
|  1 | SIMPLE      | ev0   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_DEV01.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev1   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_DEV01.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev2   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_DEV01.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev3   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_DEV01.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev4   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_DEV01.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev5   | ref    | IX_EVENT_ID_EVENT_TYPE                                                                                                                 | IX_EVENT_ID_EVENT_TYPE           | 36      | UNIVIS_DEV01.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | psi   | ref    | PRIMARY,IX_po_shipment_item_po_shipment1,FK_po_shipment_item_po_shipment1                                                              | IX_po_shipment_item_po_shipment1 | 4       | UNIVIS_DEV01.ps.ship_id       |    1 |                                              | 
|  1 | SIMPLE      | pa    | ref    | IX_po_alloc_po_shipment_item2,IX_po_alloc_po_details_old,FK_po_alloc_po_shipment1,FK_po_alloc_po_shipment_item1,FK_po_alloc_po_header1 | IX_po_alloc_po_shipment_item2    | 4       | UNIVIS_DEV01.ps.ship_id       |    3 | Using where                                  | 
|  1 | SIMPLE      | ph    | eq_ref | PRIMARY,IX_HDR_ID                                                                                                                      | PRIMARY                          | 4       | UNIVIS_DEV01.pa.hdr_id        |    1 |                                              | 
+----+-------------+-------+--------+----------------------------------------------------------------------------------------------------------------------------------------+----------------------------------+---------+-------------------------------+------+----------------------------------------------+

更新这绝对是数据相关的。我从数据库 A 中转储了数据并将其加载到数据库 B 中:

SELECT * from <table> into outfile <file>

LOAD DATA INFILE <file> into table <table>

然后数据库 B 查询运行得很快——即。与数据库 A 一样快。关于如何诊断数据可能有问题的任何想法?

更新 @newtover: 来自数据库 A:

+-----------------+---------------------+
| eta_selectivity | ship_id_selectivity |
+-----------------+---------------------+
|          0.0693 |              1.0000 | 
+-----------------+---------------------+
1 row in set (0.02 sec)

来自数据库 B(坏的)

+-----------------+---------------------+
| eta_selectivity | ship_id_selectivity |
+-----------------+---------------------+
|          0.1814 |              1.0000 | 
+-----------------+---------------------+
1 row in set (0.02 sec)

节目为 po_shipment 创建:

| po_shipment | CREATE TABLE `po_shipment` (
  `ship_id` int(11) NOT NULL DEFAULT '0',
  `ship_type` varchar(16) DEFAULT NULL,
  `foreign_agent` varchar(16) DEFAULT NULL,
  `agent_ref` varchar(16) DEFAULT NULL,
  `exporter_code` varchar(30) DEFAULT NULL,
  `importer_code` varchar(30) DEFAULT NULL,
  `carrier_code` varchar(30) DEFAULT NULL,
  `exporter_name` varchar(50) DEFAULT NULL,
  `importer_name` varchar(50) DEFAULT NULL,
  `carrier_name` varchar(50) DEFAULT NULL,
  `receipt` varchar(30) DEFAULT NULL,
  `pol_aol` varchar(50) DEFAULT NULL,
  `pod_aod` varchar(30) DEFAULT NULL,
  `final_dest` varchar(50) DEFAULT NULL,
  `vessel_flno` varchar(30) DEFAULT NULL,
  `ets` date DEFAULT NULL,
  `eta` date DEFAULT NULL,
  `pieces` int(11) DEFAULT '0',
  `weight` decimal(17,2) DEFAULT '0.00',
  `volume` decimal(17,2) DEFAULT '0.00',
  `marks` varchar(500) DEFAULT NULL,
  `goods_desc` varchar(500) DEFAULT NULL,
  `ship_terms` varchar(16) DEFAULT NULL,
  `ship_terms_desc` varchar(50) DEFAULT NULL,
  `house_hawb` varchar(30) DEFAULT NULL,
  `ocean_mawb` varchar(30) DEFAULT NULL,
  `booking_date` date DEFAULT NULL,
  `expected_cargo` date DEFAULT NULL,
  `mfrt_jobdisp` varchar(30) DEFAULT NULL,
  `ship_complete` date DEFAULT NULL,
  `user_id` varchar(30) DEFAULT NULL,
  `receipt_desc` varchar(60) DEFAULT NULL,
  `fin_dest_desc` varchar(60) DEFAULT NULL,
  `pol_aol_desc` varchar(60) DEFAULT NULL,
  `pod_aod_desc` varchar(60) DEFAULT NULL,
  `exporter_ref` varchar(26) DEFAULT NULL,
  `carrier_ref` varchar(26) DEFAULT NULL,
  `terms_conds` date DEFAULT NULL,
  `last_amended` date DEFAULT NULL,
  `user_amended` varchar(30) DEFAULT NULL,
  `package_type` varchar(24) DEFAULT NULL,
  `ext_cancelled` tinyint(1) NOT NULL DEFAULT '0',
  `ext_goh` tinyint(1) NOT NULL DEFAULT '0',
  `ext_arrival_date` date DEFAULT NULL,
  `ext_booking_ref` varchar(255) DEFAULT NULL,
  `ext_dc_booked_delivery_date` date DEFAULT NULL,
  `ext_dc_booked_delivery_time` varchar(10) DEFAULT NULL,
  `ext_comments` text,
  `deleted` tinyint(1) NOT NULL DEFAULT '0',
  `last_amended_time` int(10) DEFAULT NULL,
  `last_amended_uni` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`ship_id`),
  KEY `IX_ETA_DATE` (`eta`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 | 

更新 @chris_I 如果我通过删除除 EVENT_TABLE 之外的所有其他连接来剥离查询,我会得到相同的性能(即糟糕)

SELECT * FROM po_shipment ps 
LEFT JOIN EVENT_TABLE ev0 ON ev0.TABLE_ID1 = ps.ship_id AND ev0.EVENT_TYPE = 'MAS0' 
LEFT JOIN EVENT_TABLE ev1 ON ev1.TABLE_ID1 = ps.ship_id AND ev1.EVENT_TYPE = 'MAS1' 
LEFT JOIN EVENT_TABLE ev2 ON ev2.TABLE_ID1 = ps.ship_id AND ev2.EVENT_TYPE = 'MAS2' 
LEFT JOIN EVENT_TABLE ev3 ON ev3.TABLE_ID1 = ps.ship_id AND ev3.EVENT_TYPE = 'MAS3' 
LEFT JOIN EVENT_TABLE ev4 ON ev4.TABLE_ID1 = ps.ship_id AND ev4.EVENT_TYPE = 'MAS4' 
LEFT JOIN EVENT_TABLE ev5 ON ev5.TABLE_ID1 = ps.ship_id AND ev5.EVENT_TYPE = 'MAS5' 
WHERE ps.eta >= '2010-03-22'
GROUP BY ps.ship_id
LIMIT 100;

更新 @Marcus Adams: 查询已删除内部联接的计划:

SELECT * FROM po_shipment ps 
LEFT JOIN EVENT_TABLE ev0 ON ev0.TABLE_ID1 = ps.ship_id AND ev0.EVENT_TYPE = 'MAS0' 
LEFT JOIN EVENT_TABLE ev1 ON ev1.TABLE_ID1 = ps.ship_id AND ev1.EVENT_TYPE = 'MAS1' 
LEFT JOIN EVENT_TABLE ev2 ON ev2.TABLE_ID1 = ps.ship_id AND ev2.EVENT_TYPE = 'MAS2' 
LEFT JOIN EVENT_TABLE ev3 ON ev3.TABLE_ID1 = ps.ship_id AND ev3.EVENT_TYPE = 'MAS3' 
LEFT JOIN EVENT_TABLE ev4 ON ev4.TABLE_ID1 = ps.ship_id AND ev4.EVENT_TYPE = 'MAS4' 
LEFT JOIN EVENT_TABLE ev5 ON ev5.TABLE_ID1 = ps.ship_id AND ev5.EVENT_TYPE = 'MAS5' 
WHERE ps.eta >= '2010-03-22'
GROUP BY ps.ship_id
LIMIT 100;

来自数据库 A 的查询计划(0.35 秒内响应)

+----+-------------+-------+-------+------------------------+------------------------+---------+------------------------------+------+----------------------------------------------+
| id | select_type | table | type  | possible_keys          | key                    | key_len | ref                          | rows | Extra                                        |
+----+-------------+-------+-------+------------------------+------------------------+---------+------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | ps    | range | IX_ETA_DATE            | IX_ETA_DATE            | 4       | NULL                         |  174 | Using where; Using temporary; Using filesort | 
|  1 | SIMPLE      | ev0   | ref   | IX_EVENT_ID_EVENT_TYPE | IX_EVENT_ID_EVENT_TYPE | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev1   | ref   | IX_EVENT_ID_EVENT_TYPE | IX_EVENT_ID_EVENT_TYPE | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev2   | ref   | IX_EVENT_ID_EVENT_TYPE | IX_EVENT_ID_EVENT_TYPE | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev3   | ref   | IX_EVENT_ID_EVENT_TYPE | IX_EVENT_ID_EVENT_TYPE | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev4   | ref   | IX_EVENT_ID_EVENT_TYPE | IX_EVENT_ID_EVENT_TYPE | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |                                              | 
|  1 | SIMPLE      | ev5   | ref   | IX_EVENT_ID_EVENT_TYPE | IX_EVENT_ID_EVENT_TYPE | 36      | UNIVIS_PROD.ps.ship_id,const |    1 |                                              | 
+----+-------------+-------+-------+------------------------+------------------------+---------+------------------------------+------+----------------------------------------------

来自数据库 B 的查询计划(没有及时响应泡茶所需的时间)

    +----+-------------+-------+-------+------------------------+------------------------+---------+-------------------------------+------+----------------------------------------------+
| id | select_type | table | type  | possible_keys          | key                    | key_len | ref                           | rows | Extra                                        |
+----+-------------+-------+-------+------------------------+------------------------+---------+-------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | ps    | range | IX_ETA_DATE            | IX_ETA_DATE            | 4       | NULL                          |   38 | Using where; Using temporary; Using filesort | 
|  1 | SIMPLE      | ev0   | ref   | IX_EVENT_ID_EVENT_TYPE | IX_EVENT_ID_EVENT_TYPE | 36    

【问题讨论】:

  • 您能否提供来自每个数据库的DESCRIBE EVENT_TABLE... 我很感兴趣,解释计划因每个 evX 连接而异。
  • 我在回答中添加了一些想法。
  • 如果您删除绝对所有联接,性能如何。前任。 SELECT * FROM po_shipment ps WHERE ps.eta >= '2010-03-22' GROUP BY ps.ship_id LIMIT 100; ?
  • 我们能否看到查询的解释结果,而不需要两个数据库的内部连接?
  • @PHP_Jedi:如果我删除所有连接,那么它的速度非常快。如上所述,如果我做以下三件事之一:删除连接、删除条件、删除分组;然后很快。

标签: mysql optimization sql-execution-plan


【解决方案1】:

尝试将 STRAIGHT_JOIN 添加到查询中以查看执行计划是否是问题所在。优化器正在为每个数据库选择不同的执行计划,这可能会导致问题。

SELECT STRAIGHT_JOIN * FROM po_shipment ps 
LEFT JOIN EVENT_TABLE ev0 ON ev0.TABLE_ID1 = ps.ship_id AND ev0.EVENT_TYPE = 'MAS0' 
LEFT JOIN EVENT_TABLE ev1 ON ev1.TABLE_ID1 = ps.ship_id AND ev1.EVENT_TYPE = 'MAS1' 
LEFT JOIN EVENT_TABLE ev2 ON ev2.TABLE_ID1 = ps.ship_id AND ev2.EVENT_TYPE = 'MAS2' 
LEFT JOIN EVENT_TABLE ev3 ON ev3.TABLE_ID1 = ps.ship_id AND ev3.EVENT_TYPE = 'MAS3' 
LEFT JOIN EVENT_TABLE ev4 ON ev4.TABLE_ID1 = ps.ship_id AND ev4.EVENT_TYPE = 'MAS4' 
LEFT JOIN EVENT_TABLE ev5 ON ev5.TABLE_ID1 = ps.ship_id AND ev5.EVENT_TYPE = 'MAS5' 
JOIN po_shipment_item psi USING (ship_id) 
JOIN po_alloc pa ON ps.ship_id = pa.ship_id AND pa.UID_items = psi.UID_items 
JOIN po_header ph ON pa.hdr_id = ph.hdr_id
WHERE ps.eta >= '2010-03-22'
GROUP BY ps.ship_id
LIMIT 100;

更新
1. 数据库 A 的新执行计划不需要文件排序或临时表,因为它使用主键。我会开始在查询中添加USE INDEX,看看是否可以加快数据库B的查询速度。ship_id是po_shipment的主键吗?如果是这样,您需要根据日期或过滤日期来确定组的成本更高。

SELECT STRAIGHT_JOIN * FROM po_shipment ps USE INDEX( PRIMARY )
LEFT JOIN EVENT_TABLE ev0 ON ev0.TABLE_ID1 = ps.ship_id AND ev0.EVENT_TYPE = 'MAS0' 
LEFT JOIN EVENT_TABLE ev1 ON ev1.TABLE_ID1 = ps.ship_id AND ev1.EVENT_TYPE = 'MAS1' 
LEFT JOIN EVENT_TABLE ev2 ON ev2.TABLE_ID1 = ps.ship_id AND ev2.EVENT_TYPE = 'MAS2' 
LEFT JOIN EVENT_TABLE ev3 ON ev3.TABLE_ID1 = ps.ship_id AND ev3.EVENT_TYPE = 'MAS3' 
LEFT JOIN EVENT_TABLE ev4 ON ev4.TABLE_ID1 = ps.ship_id AND ev4.EVENT_TYPE = 'MAS4' 
LEFT JOIN EVENT_TABLE ev5 ON ev5.TABLE_ID1 = ps.ship_id AND ev5.EVENT_TYPE = 'MAS5' 
JOIN po_shipment_item psi USING (ship_id) 
JOIN po_alloc pa ON ps.ship_id = pa.ship_id AND pa.UID_items = psi.UID_items 
JOIN po_header ph ON pa.hdr_id = ph.hdr_id
WHERE ps.eta >= '2010-03-22'
GROUP BY ps.ship_id
LIMIT 100;

如果这没有帮助,请尝试为数据库 B 建议数据库 A 的执行计划中使用的更多索引。

【讨论】:

  • 谢谢,有趣的选择。没有改变最终结果。数据库 B 仍然挂起。但是查询计划已经改变——两者仍然不同。我已经更新了问题。
  • 它删除数据库 A 上的文件排序和临时表的原因是因为它使用了 ps 的主键。尝试添加 USE INDEX,我会在一分钟内添加查询。
【解决方案2】:

如果是数据问题,我不能告诉你确切的问题是什么,但这是我最喜欢解决这类问题的策略:

尝试删除一半的联接。递归重复,直到查询快速运行。然后添加您在上一步中删除的一半联接...(此策略所需的步骤比通过联接删除和添加联接要少得多。)

一旦发现“坏”连接,您可以尝试使用附加的“where”子句限制其值,直到查询再次快速运行...在每一步中,始终尝试将问题减少一半.

注意:很可能是这样,即使数据库 B 中的数据总量较小,您也会为连接的中间结果获得更多记录。

【讨论】:

  • 我已经做到了。请参阅我的问题中的“注意事项”。删除 EVENT_TABLE 上的连接会导致它在删除每个连接时加速。
  • @Damo:如果个额外的 EVENT_TABLE 连接导致额外的时间,那么主要问题很可能位于其他连接之一!然后 EVENT_TABLE 只需乘以所需的时间。
  • @Chris_I 我已经更新了问题。我可以删除所有其他连接,只留下 EVENT_TABLEs,我得到相同的效果。
  • @Damo: po_shipment 本身有多大(在数据库 A 和 B 中)?
  • @Damo:我明白了,你已经回答了这个问题:1776 vs 463。这不是一个很大的区别。如果行很大,也许它仍然足以耗尽一些临时空间?我已经(也)看到过 MaxDB 的这种行为——顺便说一下,Postgres 的问题更少。
【解决方案3】:

有一段时间没有接触 MySQL,但我猜这个问题与任何一个都有关

  1. 您是否检查过您的键/连接字段长度(实际数据)是否可能导致 sort_buffer 在大(大小)键上分页到磁盘连接? (这闻起来像是数据问题……)
  2. 服务器设置,基本上是写入内存临时表。几年前我也有类似的事情。是否放大了 key_buffer_size、table_cache、read_rnd_buffer_size、sort_buffer、read_buffer_size 看看是否有帮助?

【讨论】:

  • +1 - 你让我想到了这个。更新了问题,包含一些关于键的有趣信息
  • 对我来说也像数据问题。尝试减小键的大小并增加缓冲区,但到目前为止没有任何效果。
【解决方案4】:

更新您的统计信息。有一次我有类似的问题,这对我有用。

【讨论】:

  • 如果你的意思是 ANALYZE TABLE 那么我以前也有过这种情况,我已经对两个数据库上与查询有关的所有表都这样做了,但没有改变。
【解决方案5】:

由于它是 InnoDB,它看起来可能是一个锁定问题。同时还有什么事情发生?

【讨论】:

  • 什么都没有。两个数据库都已拉回我的开发机器。
【解决方案6】:

由于数据似乎存在问题,因此找出导致问题的数据可能会有所帮助。创建第三个数据库 C 并将数据库 B 中的一半数据插入两次(因此您拥有相同的行数)。如果数据库 C 慢,那么坏数据就在那里,否则就在另一半。重复使用越来越小的块大小以帮助找到问题数据。

即使数据库 B 比数据库 A 小,表 'po_header' 和 'EVENT_TABLE' 也不会按比例变小。这可能与速度差异有关。

【讨论】:

    【解决方案7】:

    必须尝试执行“检查表”和/或“优化表”吗?

    我有一个similar situation,问题是关键索引没有正确更新并且执行 CHECK TABLE 修复了它们。远射,但值得一试。

    【讨论】:

    • 谢谢。在查询中涉及的所有表上运行 CHECK TABLE 和 OPTIMIZE。没有效果。
    【解决方案8】:

    首先确保您已在适当的字段上创建索引。我相信你已经做到了。

    接下来,尝试使用索引提示 (USE INDEX) 强制数据库正确使用索引。

    我有一个similar problem,在其中我假设索引已正确设置并由 mysql 使用,但事实并非如此。我能够使用索引提示来解决它。

    【讨论】:

      【解决方案9】:

      po_shipment.eta 和 po_shipment.ship_id 中数据的选择性是什么。您能否从两个数据库中发布以下查询的结果:

      SELECT
          count(distinct eta)/count(*) as eta_selectivity,
          count(distinct ship_id)/count(*) as ship_id_selectivity
      FROM po_shipment;
      

      通常,字段数据的选择性越强(接近 1),索引工作的效果就越好。如果优化器缺少必要的统计信息,原因也可能是 po_shipment.eta 中的数据分布非常不均匀(当您使用 '2099-01-01' 等作为特殊值时)。

      顺便问一下,你能提供 SHOW CREATE TABLE po_shipment 吗?桌面上的指数可以提供一些启示。

      UPD:当字段的选择性低到eta字段时,索引基本没用。更糟糕的是,它可能会在选择时混淆优化器并减慢数据的插入和更新。

      第一个建议是尝试删除eta 字段上的索引并测量结果。可能A数据库的优化器甚至没有尝试使用索引,因为选择性非常低,而对于B数据库它尝试了索引。

      困扰我的第二件事是你为什么首先按 ship_id 分组?当需要隐式临时表并且在字段中有一个 TEXT(如您的情况)或 BLOB 时,MySQL 将始终在磁盘上使用临时表进行排序(这在 GROUP BY 中是隐式要求的)。在您的情况下,ship_id 是集群主键,结果无论如何都会按 ship_id 排序。您需要提取一个基本查询(如果可能有多个对应关系,则已按 ship_id 分组)并将 po_shipment 与基本查询应用您的范围条件,而不是在顶部使用 group by。

      第三个。当您在顶部使用 * 时,您真的需要所有字段吗?加入 10 张桌子后,您会收到很多信息。我几乎不相信你需要它们。即使从结果中排除 TEXT 字段也可能会提高查询性能。

      【讨论】:

      • 这是一个简化的查询来说明问题。 “真正的”查询实际上是一个带有更多连接和一组特定字段的 Hibernate 查询,而不是 *.话虽如此,我可能会放弃 GROUP BY。我会检查结果。
      【解决方案10】:

      我在使用范围选择器 less than greater than > 时也遇到过同样的情况。

      实验:如果范围不是太大,你有没有试过把范围扩大成 IN (...,...,...) 语句 instad ?

      例如

      SELECT * FROM po_shipment ps USE INDEX (IX_ETA_DATE)
      JOIN po_shipment_item psi USING (ship_id) 
      JOIN po_alloc pa ON ps.ship_id = pa.ship_id AND pa.UID_items = psi.UID_items 
      JOIN po_header ph ON pa.hdr_id = ph.hdr_id 
      LEFT JOIN EVENT_TABLE ev0 ON ev0.TABLE_ID1 = ps.ship_id AND ev0.EVENT_TYPE = 'MAS0' 
      LEFT JOIN EVENT_TABLE ev1 ON ev1.TABLE_ID1 = ps.ship_id AND ev1.EVENT_TYPE = 'MAS1' 
      LEFT JOIN EVENT_TABLE ev2 ON ev2.TABLE_ID1 = ps.ship_id AND ev2.EVENT_TYPE = 'MAS2' 
      LEFT JOIN EVENT_TABLE ev3 ON ev3.TABLE_ID1 = ps.ship_id AND ev3.EVENT_TYPE = 'MAS3' 
      LEFT JOIN EVENT_TABLE ev4 ON ev4.TABLE_ID1 = ps.ship_id AND ev4.EVENT_TYPE = 'MAS4' 
      LEFT JOIN EVENT_TABLE ev5 ON ev5.TABLE_ID1 = ps.ship_id AND ev5.EVENT_TYPE = 'MAS5' 
      WHERE ps.eta IN ('2010-03-22','2010-03-21','2010-03-20',...)
      GROUP BY ps.ship_id
      LIMIT 100;
      

      编辑:按照 Salman A 的建议添加了 USE INDEX() 提示。似乎 mysql 看到了可能的索引,但选择不使用它...值得测试。

      【讨论】:

      • 如果我像这样添加一个 IN,那么它会很快响应。但是,如果我收紧范围条件,它会更快地响应。例如 ps.eta IN ('2010-03-19', '2010-03-20', '2010-03-21') 在 0.14 秒内响应,而 ps.eta >= '2010-03-19' 和 ps .eta
      【解决方案11】:

      我认为可能是导致明显不足的 GROUP BY 子句的手动排序(文件排序)。

      尝试使用 SQL_BIG_RESULT 提示,看看 MySQL 是否会改进其处理 GROUP BY 的方法。

      SELECT SQL_BIG_RESULT * FROM ...
      

      【讨论】:

      • 这很有趣。以前,当我查看连接状态时,它总是“正在复制到 tmp 表”,现在有了这个提示,它是“发送数据”。不过性能没有提升。只是长时间保持这种状态。
      • @Damo - 发送数据 - 这不意味着查询完成​​了吗?结果大约有多少数据。包括。 100KB,1MB 更多?您使用什么网络连接,或者是本地主机?只是想,因为您从很多表中选择了所有内容 (*)。 E.x 将慢速结果导出到 .TXT 或 .CSV 并检查文件大小。
      • 这是本地主机,没有那么多数据(限制 100)。没有理由它应该发送这么长时间的数据。
      【解决方案12】:

      我认为你的做法是错误的。当您执行 LEFT JOIN 时,您将取回 EVENT_TABLE 中的所有记录,无论是否与 po_shipment 匹配。

      您已经对 EVENT_TABLE 运行了六次 LEFT JOIN。每个查询都会返回 (6 * (SELECT count(*) FROM EVENT_TABLE) ) 记录。由于 db A 的记录比 db B 少,当然查询在 A 上运行得更快。

      我认为这样的事情会表现得更好:

      DECLARE @TEMP_EVENT_TYPES table 
      (
          EVENT_TYPE varchar(10) PRIMARY KEY
      )
      ;
      /*
      INSERT VALUES 'MAS0', 'MAS1', 'MAS2', 'MAS3', 'MAS4', 'MAS5'
      */
      
      ;
      SELECT * FROM po_shipment ps 
      JOIN po_shipment_item psi USING (ship_id) 
      JOIN po_alloc pa ON ps.ship_id = pa.ship_id AND pa.UID_items = psi.UID_items 
      JOIN po_header ph ON pa.hdr_id = ph.hdr_id 
      INNER JOIN 
          EVENT_TABLE et ON et.TABLE_ID1 = ps.ship_id
      INNER JOIN 
          @TEMP_EVENT_TYPES tet ON tet.event_type = et.EVENT_TYPE
      WHERE ps.eta >= '2010-03-22'
      GROUP BY ps.ship_id
      

      【讨论】:

      • 这不是只有在从 EVENT_TABLE 到 EVENT_TABLE 的 LEFT JOIN 时才成立。他这样做的方式,LEFT JOINing to EVENT_TABLE,只是意味着如果连接失败,那么他仍然会在 po_shipment 中获得该行的结果。
      • @Echo:完全正确。即使没有那个 EVENT_TYPE,我仍然需要该行。
      【解决方案13】:

      看起来您正在为 po_shipment 建模一个状态机,其中包含每个状态转换的 event_table 中的事件记录。

      你想用这个查询表达什么业务逻辑?

      您应该能够在您的 po_shipment 中添加一个 state 字段来完成此操作,它汇总并非规范化 event_table 中的一系列事件。

      构建逻辑以验证给定记录的所有 6 个事件是否已发生不应在数据库中,它应该在您的模型中并持久保存在数据库中(模型的经典状态机模式)。

      然后它只是从 po_shipment 中选择 state = 'MAS5' and eta > date;

      【讨论】:

      • 当然不是状态机。它只是一个带有一些可配置事件的 Shipment,这些事件是按顺序完成的。在 UI 中,您会看到 Shipment 数据,然后是事件的历史记录。
      • 如果您不能完全更改查询的结构,我敢打赌您可以将其归结为 event_table 上的单个联接。加入一次条件: ... event_type IN ('MAS0', 'MAS1', ... 'MAS5')
      【解决方案14】:

      出于好奇 - B 中的原始数据是否有很多 NULL?

      【讨论】:

      • 两个数据库都有很多 NULL。尤其是在加入的 EVENT_TABLEs 中。
      猜你喜欢
      • 2014-06-20
      • 2018-08-29
      • 1970-01-01
      • 2023-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多