回顾
上一篇了解了分析SQL使用的explain
,可以点击查看MySQL篇-SQL优化实战了解我在写sql的注意事项还有explain
的说明,这次拿一段生产使用的sql
进行优化说明。从14s
优化到2.6s
待优化的SQL
SELECT DISTINCT
swpe.tag_number,
hca.ACCOUNT_NAME customer_name,
sipa.PIN_LOGO area_number,
cdla.delivery_header_id,
swpe.pack_number,
swph.packslip_number,
cdpa.transport_mode,
date_format(
swpe.inware_date,
'%Y-%m-%d %H:%i:%s'
) in_warehouse_date,
DATE(cdla.act_delivery_date) act_delivery_date,
cdla.plate_number,
wbp.PLATFORM_NAME schedule_stage_mir,
sooh.order_number,
swph.lot_number,
milk.ATTRIBUTE14,
ifnull(
(
SELECT
'Y'
FROM
cwms_delivery_attachment_all cda
WHERE
cda.pack_entity_id = swpe.pack_entity_id
AND cda.stock_scan_status = 'Y'
LIMIT 1
),
'N'
) stock_status,
ifnull(
(
SELECT
cda.comments
FROM
cwms_delivery_attachment_all cda
WHERE
cda.pack_entity_id = swpe.pack_entity_id
LIMIT 1
),
NULL
) comments,
ifnull(
(
SELECT
swdh.delivery_number
FROM
sfy_wsh_delivery_lines_all swdl
INNER JOIN sfy_wsh_delivery_headers_all swdh ON swdl.delivery_header_id = swdh.delivery_header_id
WHERE
swpe.tag_number = swdl.tag_number
LIMIT 1
),
NULL
) delivery_number,
ifnull(
(
SELECT
filter1
FROM
eos_dict_entry
WHERE
DICTTYPEID = 'AUTH_CONFIG'
LIMIT 1
),
'Y'
) zc_power,
ppl.PICK_NUMBER pd_number,
IF (
ifnull(
(
SELECT
IF (
substr(father.license_number, 1, 2) = 'TP',
father.license_number,
NULL
) tp_number
FROM
wms_mtl_onhand_quantities_detail child
INNER JOIN wms_mtl_onhand_quantities_detail father ON child.parent_mq_id = father.mq_id
WHERE
child.license_number = swpe.tag_number
LIMIT 1
),
1
) != 1,
(
SELECT
IF (
substr(father.license_number, 1, 2) = 'TP',
father.license_number,
NULL
) tp_number
FROM
wms_mtl_onhand_quantities_detail child
INNER JOIN wms_mtl_onhand_quantities_detail father ON child.parent_mq_id = father.mq_id
WHERE
child.license_number = swpe.tag_number
LIMIT 1
),
(
SELECT
IF (
substr(
oldfather.license_number,
1,
2
) = 'TP',
oldfather.license_number,
NULL
) tp_number
FROM
wms_mtl_onhand_quantities_detail child
INNER JOIN wms_mtl_onhand_quantities_detail father ON child.parent_mq_id = father.mq_id
INNER JOIN wms_mtl_onhand_quantities_detail oldfather ON father.parent_mq_id = oldfather.mq_id
WHERE
child.license_number = swpe.tag_number
LIMIT 1
)
) tp_number,
IF (
(
SELECT
DOWNRACKSTYPE
FROM
wms_mtl_onhand_quantities_detail
WHERE
license_number = swpe.tag_number
LIMIT 1
) = 3,
'Y',
'N'
) type,
(
SELECT
TC_NUMBER
FROM
cwms_mobile_tray_lines cmtl
WHERE
cmtl.TAG_NUMBER = swpe.tag_number
ORDER BY
CREATION_DATE DESC
LIMIT 1
) tc_number,
IF (
(
SELECT
DOWNRACKSTYPE
FROM
wms_mtl_onhand_quantities_detail
WHERE
license_number = swpe.tag_number
LIMIT 1
) = 3,
1,
0
) is_scan
FROM
cwms_delivery_lines_all cdla
INNER JOIN hz_cust_accounts hca ON cdla.customer_id = hca.cust_account_id
INNER JOIN sfy_oe_order_headers_all sooh ON sooh.oe_header_id = cdla.oe_header_id
INNER JOIN sfy_wsh_pack_entities swpe ON cdla.oe_header_id = swpe.oe_header_id
INNER JOIN sfy_wsh_packslip_headers_all swph ON swpe.header_id = swph.header_id
INNER JOIN cwms_delivery_plan_all cdpa ON cdpa.DELIVERY_HEADER_ID = cdla.DELIVERY_HEADER_ID
LEFT JOIN mtl_secondary_inventories msit ON msit.organization_id = swpe.organization_id
AND msit.secondary_inventory_name = swpe.subinventory_code
LEFT JOIN pick_pack_link ppl ON ppl.TAG_NUMBER = swpe.TAG_NUMBER
LEFT JOIN mtl_item_locations_kfv milk ON swpe.LOCATOR_ID = milk.INVENTORY_LOCATION_ID
AND milk.ENABLE_FLAG = 1
LEFT JOIN sfy_inv_pd_agent sipa ON sipa.CUST_ACCOUNT_ID = hca.CUST_ACCOUNT_ID
AND sipa.ORGANIZATION_ID = swpe.ORGANIZATION_ID
LEFT JOIN (
SELECT
DELIVERY_HEADER_ID,
SCHEDULE_STAGE,
group_concat(CAR_NUMBER) CAR_NUMBER,
group_concat(DISTINCT LOGISTIC_PROVIDER) LOGISTIC_PROVIDER
FROM
wms_delivery_car_detail
GROUP BY
DELIVERY_HEADER_ID
) wdcd ON cdpa.DELIVERY_HEADER_ID = wdcd.DELIVERY_HEADER_ID
LEFT JOIN wms_bill_platform wbp ON wbp.PLATFORM_CODE = wdcd.SCHEDULE_STAGE
AND wbp.PLATFORM_ENABLE_FLAG = 1
WHERE
swpe.tag_number IS NOT NULL
AND swpe.pack_number IS NOT NULL
AND swpe. STATUS != 'X'
AND cdpa.approve_status = 'Y'
AND cdpa.inv_approve_status = 'Y'
AND hca.account_number = 'GPS21017802'
AND cdla.act_delivery_date = '2024-06-26'
AND ifnull(
swpe.delivery_date,
cdla.act_delivery_date
) >= cdla.act_delivery_date
ORDER BY
milk.CONCATENATED_SEGMENTS
1、问题展示
查询效率:3w
条数据,耗费14s
2、问题排查
2.1、操作思路
通过执行计划看到wms_delivery_car_detail
的执行计划好像有优化空间,先将这张表的关联移除后查看执行效率,以确认这张表影响的程度
2.2、执行结果
查询效率13.9s
,发现移除后并没有显著的提高,说明这个子查询的执行计划并没有 很大的效率问题
3、大胆假设,小心求证
3.1、操作思路
那么接下来看看其他执行计划,发现而且在字段上的子查询有很多,假如我们把所有字段中的子查询都移除了会有什么效果——移除了执行计划中select_type=DEPENDENT SUBQUERY
的子查询后,只需要1.3s
就拿到查询结果了,执行计划如图所示,由此可知字段上的子查询多了,结果集大时会对查询效率有很大的影响。
既然找到了问题出现在子查询上,但这些字段还是要查的,只是我们得换种方式,目的是移除子查询的情况下依然查询所需字段,那就要修改为连接查询的方式,如先把关联相同表的多个子查询通过表关联的方式合并为一次关联。
3.2、子查询分析
子查询表 | 对应的子查询数量 | 代码行数 |
---|---|---|
cwms_delivery_attachment_all | 2个 | 第21至28,34至40 |
wms_mtl_onhand_quantities_detail | 2个 | 第86至95,100至112,115至132,138至144 |
sfy_wsh_delivery_headers_all | 1个 | 第55至62 |
cwms_mobile_tray_lines | 1个 | 第150至158(因为其中的tag_number是唯一的,经业务确认此处的order by 可移除) |
3.3、执行结果
修改为连接查询后,查询耗时仅需要2.6s
,执行计划如下:
-- 修改后的sql
SELECT DISTINCT
swpe.tag_number,
hca.ACCOUNT_NAME customer_name,
sipa.PIN_LOGO area_number,
cdla.delivery_header_id,
swpe.pack_number,
swph.packslip_number,
cdpa.transport_mode,
date_format(
swpe.inware_date,
'%Y-%m-%d %H:%i:%s'
) in_warehouse_date,
DATE(cdla.act_delivery_date) act_delivery_date,
wbp.PLATFORM_NAME schedule_stage_mir,
sooh.order_number,
swph.lot_number,
milk.ATTRIBUTE14,
ifnull(cdaa.stock_scan_status, 'N') stock_status,
ifnull(cdaa.comments, NULL) comments,
ifnull(swdh.delivery_number, NULL) delivery_number,
ifnull(
(
SELECT
filter1
FROM
eos_dict_entry
WHERE
DICTTYPEID = 'AUTH_CONFIG'
LIMIT 1
),
'Y'
) zc_power,
ppl.PICK_NUMBER pd_number,
IF (
-- wmoqd.TP_NUMBER是原关联fater.license_number或者oldfather.license_number的值
substr(wmoqd.TP_NUMBER, 1, 2) = 'TP',
wmoqd.TP_NUMBER,
NULL
) tp_number,
IF (wmoqd.DOWNRACKSTYPE = 3, 'Y', 'N') type,
cmtl.TC_NUMBER tc_number,
IF (wmoqd.DOWNRACKSTYPE = 3, 1, 0) is_scan
FROM
cwms_delivery_lines_all cdla
INNER JOIN hz_cust_accounts hca ON cdla.customer_id = hca.cust_account_id
INNER JOIN sfy_oe_order_headers_all sooh ON sooh.oe_header_id = cdla.oe_header_id
INNER JOIN sfy_wsh_pack_entities swpe ON cdla.oe_header_id = swpe.oe_header_id
INNER JOIN sfy_wsh_packslip_headers_all swph ON swpe.header_id = swph.header_id
INNER JOIN cwms_delivery_plan_all cdpa ON cdpa.DELIVERY_HEADER_ID = cdla.DELIVERY_HEADER_ID
LEFT JOIN mtl_secondary_inventories msit ON msit.organization_id = swpe.organization_id
AND msit.secondary_inventory_name = swpe.subinventory_code
LEFT JOIN pick_pack_link ppl ON ppl.TAG_NUMBER = swpe.TAG_NUMBER
LEFT JOIN mtl_item_locations_kfv milk ON swpe.LOCATOR_ID = milk.INVENTORY_LOCATION_ID
AND milk.ENABLE_FLAG = 1
LEFT JOIN sfy_inv_pd_agent sipa ON sipa.CUST_ACCOUNT_ID = hca.CUST_ACCOUNT_ID
AND sipa.ORGANIZATION_ID = swpe.ORGANIZATION_ID
LEFT JOIN (
SELECT
DELIVERY_HEADER_ID,
SCHEDULE_STAGE,
group_concat(CAR_NUMBER) CAR_NUMBER,
group_concat(DISTINCT LOGISTIC_PROVIDER) LOGISTIC_PROVIDER
FROM
wms_delivery_car_detail
GROUP BY
DELIVERY_HEADER_ID
) wdcd ON cdpa.DELIVERY_HEADER_ID = wdcd.DELIVERY_HEADER_ID
LEFT JOIN wms_bill_platform wbp ON wbp.PLATFORM_CODE = wdcd.SCHEDULE_STAGE
AND wbp.PLATFORM_ENABLE_FLAG = 1
-- 主要改动在这里:从子查询迁移到下面左关联
LEFT JOIN cwms_delivery_attachment_all cdaa ON cdaa.PACK_ENTITY_ID=swpe.PACK_ENTITY_ID
LEFT JOIN wms_mtl_onhand_quantities_detail wmoqd ON wmoqd.LICENSE_NUMBER=swpe.TAG_NUMBER AND wmoqd.ORGANIZATION_ID=swpe.ORGANIZATION_ID
LEFT JOIN sfy_wsh_delivery_lines_all swdl ON swdl.TAG_NUMBER=swpe.TAG_NUMBER
LEFT JOIN sfy_wsh_delivery_headers_all swdh ON swdh.DELIVERY_HEADER_ID=swdl.DELIVERY_HEADER_ID
LEFT JOIN cwms_mobile_tray_lines cmtl ON cmtl.TAG_NUMBER=swpe.TAG_NUMBER
WHERE
swpe.tag_number IS NOT NULL
AND swpe.pack_number IS NOT NULL
AND swpe. STATUS != 'X'
AND cdpa.approve_status = 'Y'
AND cdpa.inv_approve_status = 'Y'
AND hca.account_number = 'GPS21017802'
AND cdla.act_delivery_date = '2024-06-26'
AND ifnull(
swpe.delivery_date,
cdla.act_delivery_date
) >= cdla.act_delivery_date
GROUP BY swpe.TAG_NUMBER
ORDER BY
milk.CONCATENATED_SEGMENTS
总结
- 当结果集字段中有好几个相同表的子查询时,将子查询修改为连接查询的效率提升会比较大(相当于一行记录处理一次查询)
- 有时候执行计划可能无法直接看出修改哪里能提升,但能给我们提供优化的思路
- 在执行计划中看到每个表都走索引了,但是却还是很慢,那我们可以一段一段的、一表一表的排除,找到问题点在哪,而多快能找到主要就取决于经验还有对表的熟悉程度了。