【发布时间】:2020-03-28 13:30:36
【问题描述】:
问题的变化很大,因为我在 VPN 上而不是 MySQL 服务器的本地网络上,所以问题仍然存在,这个查询太慢了。我的完整查询现在已包含在内,我正在为连接表添加所有布局。
此查询花费的时间太长,它确实返回了一个大型数据集,但限制数据集似乎并没有帮助。我已经尝试过使用和不使用基于日期范围的 WHERE。
仍然需要 8 秒或更长时间才能返回。使用 LIMIT 也没有什么不同。
我担心每个表中的记录可能太多,在这种情况下,我唯一的选择是创建一组“工作”表,其中包含最近几周的发货量,然后将它们移到一组如果查询都会很慢的“历史”表。除非有人能找到一种方法来加快速度。 谢谢。
SELECT
ShipmentHeader.RecNbr,
ShipmentHeader.Void,
ShipmentHeader.SONum,
ShipmentHeader.DateofReq,
ShipmentHeader.ShipToName,
ShipmentHeader.ShipToCity,
ShipmentHeader.ShipToState,
ShipmentHeader.ShipToZip,
ShipmentHeader.ShipToCountry,
Carrier.RecNbr AS CarrierRecNbr,
CarrierService.RecNbr AS CarrierServiceRecNbr,
ShipmentLabelsPrintLogView.Users
FROM
ShipmentHeader
INNER JOIN ShipmentLabels
ON ShipmentHeader.RecNbr = ShipmentLabels.HeaderRecNbr
LEFT JOIN VendorService
ON ShipmentLabels.VendorServiceRecNbr = VendorService.RecNbr
LEFT JOIN CarrierService
ON VendorService.CarrierServiceRecNbr = CarrierService.RecNbr
LEFT JOIN Carrier
ON CarrierService.CarrierRecNbr = Carrier.RecNbr
LEFT JOIN ShipmentLabelsPrintLogView
ON ShipmentLabels.RecNbr = ShipmentLabelsPrintLogView.ShipmentLabelsRecNbr
ORDER BY ShipmentHeader.RecNbr DESC
以下是表结构:
CREATE TABLE `ShipmentHeader` (
`RecNbr` int(11) NOT NULL AUTO_INCREMENT,
`Void` varchar(1) NOT NULL DEFAULT 'N',
`SONum` varchar(7) NOT NULL,
`User` varchar(6) NOT NULL,
`DateofReq` datetime NOT NULL,
`ShipToName` varchar(255) DEFAULT NULL,
`ShipToAddress1` varchar(255) DEFAULT NULL,
`ShipToAddress2` varchar(255) DEFAULT NULL,
`ShipToAddress3` varchar(255) DEFAULT NULL,
`ShipToCity` varchar(255) DEFAULT NULL,
`ShipToState` varchar(255) DEFAULT NULL,
`ShipToZip` varchar(255) DEFAULT NULL,
`ShipToCountry` varchar(255) DEFAULT NULL,
`PackageHeaderRecNbr` int(11) DEFAULT NULL,
`NegotiatedShipmentCharge` decimal(10,2) DEFAULT NULL,
`PublishedShipmentCharge` decimal(10,2) DEFAULT NULL,
`CustomerShipmentCharge` decimal(10,2) DEFAULT NULL,
`VoidTimeStamp` datetime DEFAULT NULL,
`VoidUser` varchar(50) DEFAULT NULL,
PRIMARY KEY (`RecNbr`),
KEY `SONum` (`SONum`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=539140 DEFAULT CHARSET=utf8
CREATE TABLE `ShipmentLabels` (
`RecNbr` int(11) NOT NULL AUTO_INCREMENT,
`Whse` varchar(3) DEFAULT NULL,
`HeaderRecNbr` int(11) NOT NULL,
`PackageRecNbr` int(11) NOT NULL,
`Carrier` varchar(60) NOT NULL DEFAULT '',
`Service` varchar(60) DEFAULT NULL,
`Charges` decimal(10,2) NOT NULL,
`RatedCost` decimal(10,2) NOT NULL,
`PublishedRate` decimal(10,2) NOT NULL,
`CustomerCharge` decimal(10,2) NOT NULL,
`Weight` int(11) NOT NULL,
`LabelFormat` varchar(30) NOT NULL DEFAULT '',
`ShipmentID` varchar(60) NOT NULL,
`TrackingNumber` varchar(60) NOT NULL DEFAULT '',
`Label` mediumtext NOT NULL COMMENT 'Base64 Encoded',
`DateofLabel` datetime NOT NULL,
`LabelSource` varchar(50) DEFAULT NULL,
`VendorServiceRecNbr` int(11) DEFAULT NULL,
PRIMARY KEY (`RecNbr`),
KEY `HeaderRecNbr` (`HeaderRecNbr`) USING BTREE,
KEY `PackageRecNbr` (`PackageRecNbr`) USING BTREE,
CONSTRAINT `ShipmentLabels_ibfk_1` FOREIGN KEY (`HeaderRecNbr`) REFERENCES `ShipmentHeader` (`RecNbr`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=542542 DEFAULT CHARSET=utf8
CREATE TABLE `VendorService` (
`RecNbr` int(11) NOT NULL AUTO_INCREMENT,
`CarrierServiceRecNbr` int(11) DEFAULT NULL,
`VendorRecNbr` int(11) DEFAULT NULL,
`VendorServiceCode` varchar(255) DEFAULT NULL,
`VendorServiceDesc` varchar(255) DEFAULT NULL,
`PackageType` varchar(60) DEFAULT NULL,
`LabelServiceCode` varchar(255) DEFAULT NULL,
`LabelPackageType` varchar(255) DEFAULT NULL,
PRIMARY KEY (`RecNbr`),
UNIQUE KEY `Unique` (`CarrierServiceRecNbr`,`VendorRecNbr`,`PackageType`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=196 DEFAULT CHARSET=utf8mb4;
CREATE TABLE `CarrierService` (
`RecNbr` int(11) NOT NULL AUTO_INCREMENT,
`CarrierRecNbr` int(11) DEFAULT NULL,
`CarrierServiceCode` varchar(50) DEFAULT NULL,
`CarrierServiceDesc` varchar(255) DEFAULT NULL,
PRIMARY KEY (`RecNbr`),
UNIQUE KEY `unique` (`CarrierRecNbr`,`CarrierServiceCode`)
) ENGINE=InnoDB AUTO_INCREMENT=127 DEFAULT CHARSET=utf8mb4;
CREATE TABLE `Carrier` (
`RecNbr` int(11) NOT NULL AUTO_INCREMENT,
`CarrierName` varchar(50) DEFAULT NULL,
PRIMARY KEY (`RecNbr`),
UNIQUE KEY `unique` (`CarrierName`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
我正在加入我创建的这个视图......这似乎是问题所在......但它所基于的表不是我需要的数据格式,没有这个视图。
CREATE ALGORITHM=UNDEFINED DEFINER=`atr`@`%` SQL SECURITY DEFINER VIEW `ShipmentLabelsPrintLogView`
AS
select `A`.`ShipmentLabelsRecNbr` AS `ShipmentLabelsRecNbr`,
(
select group_concat(distinct `ShipmentLabelsPrintLog`.`User`
order by `ShipmentLabelsPrintLog`.`User` ASC separator ', ')
from `ShipmentLabelsPrintLog`
where (`ShipmentLabelsPrintLog`.`ShipmentLabelsRecNbr` = `A`.`ShipmentLabelsRecNbr`)
) AS `Users`
from `ShipmentLabelsPrintLog` `A`
group by `A`.`ShipmentLabelsRecNbr`
视图所基于的表在这里:
CREATE TABLE `ShipmentLabelsPrintLog` (
`RecNbr` int(11) NOT NULL AUTO_INCREMENT,
`ShipmentLabelsRecNbr` int(11) NOT NULL,
`User` varchar(10) NOT NULL,
`Workstation` varchar(20) NOT NULL,
`DateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`RecNbr`)
) ENGINE=InnoDB AUTO_INCREMENT=5908 DEFAULT CHARSET=utf8mb4;
这里是解释:
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY ShipmentHeader index PRIMARY 4 1979351
1 PRIMARY ShipmentLabels ref HeaderRecNbr HeaderRecNbr 4 Shipping.ShipmentHeader.RecNbr 1
1 PRIMARY VendorService eq_ref PRIMARY PRIMARY 4 Shipping.ShipmentLabels.VendorServiceRecNbr 1 Using where
1 PRIMARY CarrierService eq_ref PRIMARY PRIMARY 4 Shipping.VendorService.CarrierServiceRecNbr 1 Using where
1 PRIMARY Carrier eq_ref PRIMARY PRIMARY 4 Shipping.CarrierService.CarrierRecNbr 1 Using where; Using index
1 PRIMARY <derived2> ref key0 key0 5 Shipping.ShipmentLabels.RecNbr 10 Using where
2 DERIVED A ALL 6576 Using temporary; Using filesort
3 DEPENDENT SUBQUERY ShipmentLabelsPrintLog ALL 6576 Using where
有没有办法更快地获得相同的结果?
谢谢!
【问题讨论】:
-
ShipmentLabels中的行大小似乎没有问题; MySQL 只访问HeaderRecNbr索引,它甚至没有查看基础表中的数据页。 (我们知道,因为解释输出显示“使用索引”,这是 ShipmentLabels 中唯一被引用的列。解释输出还显示 MySQL 正在避免可能昂贵的“使用文件排序”操作,满足 ORDER BY 子句,访问行集群键顺序。在我看来,大部分成本都在ShipmentHeader表的完整扫描中。 -
您是真的提取所有这些 500K+ 记录,还是在您的查询中是否有额外的 where 子句过滤记录? 500K 记录是一个巨大的数据集,大概是通过网络传输的千兆字节数据。我怀疑 this 实际上比正确执行查询需要更多时间。
-
由于您当时只显示 50 条记录,最好使用 LIMIT 和 OFFSET 子句一次仅获取 50 条记录,并且可能在您的用户界面中添加更多过滤器。例如:guru99.com/limit.html。由于您的数据集每天都在增长,因此响应时间只会随着时间的推移而增加。不要获取整个数据库,而是获取小块,然后您可以制定一个索引来优化两个表之间的连接。
-
还想补充一点,如果我包含类似 WHERE ShipmentHeader.DateofReq BETWEEN CURDATE() - INTERVAL 30 DAY AND CURDATE() 它仍然需要相同的时间,即使它只返回大约 22k 条记录.
-
即使您减小了结果集的大小,MySQL 仍然必须执行全表扫描才能获取结果,除非它可以使用索引。即使你在这个字段上有一个索引,MySQL 也不能使用它,因为它必须为每条记录评估 curdate。例如:riptutorial.com/mysql/example/23472/…。 Explain 命令将告诉您在处理查询时是否以及何时使用索引。
标签: mysql database query-optimization