需求
本来是在完成一个需求,资产转派,老麻烦了,然后还没有开始做,然后就来了一个需求,说是导出优化,对了,就是那个优化,之前就是困扰我的问题,现在更换了数据库了,从ob换成了中兴,效率又低了,虽然我真的不知道为啥国产化要换成中兴数据库,以及中兴数据库到底在哪里比较强。好了我们来处理问题
问题
sql
select
child.*,
@rank := @rank + 1 as rank_no
from
( (
select
case
RATE_ADJUSTMENT_FACTOR when '1' then '否'
else '是'
end as RATE_ADJUSTMENT_FACTOR,
FA_CATE_SEG2_CODE,
COM_CODE,
FA_BOOK_CODE,
PERIOD_NAME,
ASSET_NUMBER,
TAG_NUMBER,
ASSET_NAME,
CURRENT_UNITS,
UNIT_OF_MEASURE,
MODEL_NUMBER,
MANUFACTURER_NAME,
if(PROJECT_NUMBER = 'N/A', '', PROJECT_NUMBER ) PROJECT_NUMBER,
if(PROJECT_NAME = 'N/A', '', PROJECT_NAME ) PROJECT_NAME,
FA_CATE_CODE,
FA_CATE_DESC,
FA_KEY_DESC,
ifnull(FA_KEY_CODE, '') FA_KEY_CODE,
date_format(child.CREATION_DATE, '%Y-%m-%d') CREATION_DATE,
date_format(DATE_PLACED_IN_SERVICE, '%Y-%m-%d')
DATE_PLACED_IN_SERVICE,
round(ifnull(COST, 0.00), 2) cost,
round(ifnull(COST-DEPRN_RESERVE, 0.00), 2) netCost,
round(ifnull(DEPRN_RESERVE, 0.00), 2) DEPRN_RESERVE,
round(ifnull(SALVAGE_VALUE, 0.00), 2) SALVAGE_VALUE,
ifnull(CONVERSIONRATESTR, 0) as exchangeRate,
case
when ifnull(CONVERSIONRATESTR, 0) = '0' then '-'
else round(ifnull(COST * CONVERSIONRATESTR, 0.00), 2)
end as costForeign,
case
when ifnull(CONVERSIONRATESTR, 0) = '0' then '-'
else round(ifnull(DEPRN_RESERVE * CONVERSIONRATESTR, 0.00),
2)
end as deprnReserveForeign,
case
when ifnull(CONVERSIONRATESTR, 0) = '0' then '-'
else round(ifnull((COST - DEPRN_RESERVE)* CONVERSIONRATESTR,
0.00), 2)
end as netCostForeign,
case
when ifnull(CONVERSIONRATESTR, 0) = '0' then '-'
else round(ifnull(SALVAGE_VALUE * CONVERSIONRATESTR, 0.00),
2)
end as salvageValueForeign,
case
when ifnull(CONVERSIONRATESTR, 0) = '0' then '-'
else round(ifnull(DEPRN_AMOUNT, 0.00), 2)
end as deprnAmountForeign,
case
when ifnull(CONVERSIONRATESTR, 0) = '0' then '-'
else round(ifnull(JZZB_DEPRN_RESERVE, 0.00), 2)
end as jzzbDeprnReserveForeign,
case
when ifnull(CONVERSIONRATESTR, 0) = '0' then '-'
else round(ifnull(YTD_DEPRN, 0.00), 2)
end as ytdDeprnForeign,
round(ifnull(JZZB_DEPRN_RESERVE, 0.00), 2) JZZB_DEPRN_RESERVE,
DEPRN_AMOUNT,
CONCAT(CONCAT(CONCAT(FA_LOCATION_SEG1_CODE, '.'),
CONCAT(FA_LOCATION_SEG2_CODE, '.')), FA_LOCATION_SEG3_CODE) faLocationCode,
FA_LOCATION_SEG1_CODE,
FA_LOCATION_SEG1_DESC,
FA_LOCATION_DESC,
FA_LOCATION_SEG2_CODE,
FA_LOCATION_SEG2_DESC,
EMPLOYEE_NUMBER,
FULL_NAME,
FA_ADD_ATTRIBUTE4,
DEPRECIATE_FLAG,
EXPENSE_CODE_COMBINATION_CODE,
DEPRN_METHOD,
RESERVE_CODE_COMBINATION_CODE,
round(ifnull(YTD_DEPRN, 0.00), 2) YTD_DEPRN,
FA_LOCATION_SEG3_DESC,
FA_LOCATION_SEG3_CODE,
FA_ADD_ATTRIBUTE1,
FA_ADD_ATTRIBUTE2,
FA_ADD_ATTRIBUTE3,
FA_ADD_ATTRIBUTE5,
FA_ADD_ATTRIBUTE6,
FA_ADD_ATTRIBUTE7,
FA_ADD_ATTRIBUTE10,
FA_ADD_ATTRIBUTE11,
FA_ADD_ATTRIBUTE13,
FA_ADD_ATTRIBUTE14,
FA_ADD_ATTRIBUTE15,
FA_ADD_ATTRIBUTE29,
FA_ADD_ATTRIBUTE9,
FA_ADD_ATTRIBUTE12,
FA_ADD_ATTRIBUTE8,
FA_ADD_ATTRIBUTE16,
FA_ADD_ATTRIBUTE17,
FA_ADD_ATTRIBUTE20,
LIFE_IN_MONTHS useMonths,
(case
when (life_in_months - TIMESTAMPDIFF(month,
date_format(PRORATE_DATE, '%Y-%m-%d'), str_to_date(CONCAT(PERIOD_NAME, '-01'),
'%Y-%m-%d')) -1) < 0 then 0
when (life_in_months - TIMESTAMPDIFF(month,
date_format(PRORATE_DATE, '%Y-%m-%d'), str_to_date(CONCAT(PERIOD_NAME, '-01'),
'%Y-%m-%d')) -1) > LIFE_IN_MONTHS then LIFE_IN_MONTHS
else (life_in_months - TIMESTAMPDIFF(month,
date_format(PRORATE_DATE, '%Y-%m-%d'), str_to_date(CONCAT(PERIOD_NAME, '-01'),
'%Y-%m-%d')) -1)
end) as overMonths
from
CAMS_ASSET_DETAIL_QH_202606 as child
left join INTERNATIONAL_COMPANY_CURRENCY_EXCHANGE_RATE rate on
child.FA_BOOK_CODE = rate.BOOK_TYPE_CODE
left join INTERNATIONAL_COMPANY_EXCHANGE_RATE_LOCAL_HISTORY history on
rate.CURRENCY = history.FROM_CURRENCY
and DATE_FORMAT(history.CONVERSIONDATE, '%Y-%m-%d') =
DATE_FORMAT('2026-06-30', '%Y-%m-%d')
and TO_CURRENCY = 'CNY'
where
(period_counter_fully_retired is null
or period_counter_fully_retired = 0)
and child.FA_BOOK_CODE in ( 'CUCQH_FA_116301' ,
'CUCQH_FA_126301' , 'CUCQH_FA_126302' , 'CUCQH_FA_126303' , 'CUCQH_FA_126304' ,
'CUCQH_FA_126305' , 'CUCQH_FA_126306' , 'CUCQH_FA_126322' , 'CUCQH_FA_126323' ,
'CUCQH_FA_126328' , 'CUCQH_FA_126329' , 'CUCQH_FA_126399' )
and child.PERIOD_NAME = '2026-06' ) ) child,
(
select
@rank := 0) r
where
1 = 1
order by
asset_number
limit 300000
这个就是出现问题的sql,其实还是一个比较简单的sql,的只有一个省份的sql,查询出来的一些资产明细,里面掺杂了很多的内容,然后我稍微解释一下为啥中间看着像是多套了一层,是因为这个地方可以去多嵌套几个不同的相同的月份表的省份表,可以完成多个省份或者整个全国的导出功能,


这个是explain的结果,实话说,看到这个sql 想要优化,大概就是联查的表增加索引,后面一查确实已经加了索引,但是还是慢,确实也是跑了,然后就看了一个字段 (period_counter_fully_retired is null or period_counter_fully_retired = 0)这个地方是我觉得能够优化的点,我最初的决定创建一个字段 is_retired tinyint(1) DEFAULT NULL COMMENT 'period_counter_fully_retired IS NULL OR period_counter_fully_retired = 0 然后,
KEY idx_retired_book (is_retired,FA_BOOK_CODE)
KEY idx_base_book_asset (is_retired,FA_BOOK_CODE,ASSET_NUMBER),
创建了两个索引,然后出现了尴尬的问题,


就很尴尬,不走,然后我也不懂,开始摇人。
原理

possible_keys key rows Extra
这四个字段,possible_keys是可能使用的索引,key是真实使用的索引,rows过滤之后的行数,Extra使用条件,索引如何选择以及使用呢,就是通过里面一个索引选择器,然后这个索引选择器选择索引的依据是什么呢?
通过过滤行数,他会自动选择一个字段覆盖度和数据过滤性更加合适的,重点其实就是数据过滤性,然后尴尬的是上面的问题其实还是没有啥结果,到底要不要给一个字段要判断一下,其实不受影响的,还有就是一个表只能使用一个索引。这些东西我之前都是在背的八股文,第一次被实践了,倒是。
然后其实真的需求,是在period_counter_fully_retired、FA_BOOK_CODE、PERIOD_NAME,这三个条件上面额外增加条件。
真实条件
java
应用领域 FA_CATE_SEG1_CODE
资产目录 FA_CATE_SEG2_CODE
类别专业 FA_CATE_SEG3_CODE
资产关键字 substring_index(substring_index(child.FA_KEY_CODE, ".", 2), ".", -1) in ('01-01')
所属区域 child.FA_LOCATION_SEG1_CODE = '1263010000'
责任人 and child.EMPLOYEE_NUMBER = '0895762'
资产编号 and child.ASSET_NUMBER >= trim('1')
and child.ASSET_NUMBER <= trim('200000')
资产标签号 and child.TAG_NUMBER >= trim('121001-10044131')
and child.TAG_NUMBER <= trim('121001-10044131')
专业属性 and child.FA_ADD_ATTRIBUTE3 = '00'
资产专业类型 FA_ADD_ATTRIBUTE4
启用日期 and child.DATE_PLACED_IN_SERVICE >= '2026-08-13'
and child.DATE_PLACED_IN_SERVICE <= '2026-08-15'
创建日期
and child.CREATION_DATE >= '2026-08-14'
and child.CREATION_DATE <= '2026-08-16'
剩余月数 :
and child.overMonths >= '1'
and child.overMonths <= '2'
资产来源 and child.FA_ADD_ATTRIBUTE5 = '10'
资产归属 and child.FA_ADD_ATTRIBUTE6 = '一级干线'
是否减值 child.JZZB_DEPRN_RESERVE > 0.00
child.JZZB_DEPRN_RESERVE = 0.00
成本 and child.COST >= '1'
and child.COST <= '33'
净值 and (child.COST - child.DEPRN_RESERVE) >= '1'
and (child.COST - child.DEPRN_RESERVE) <= '44'
项目名称 PROJECT_NAME LIKE
资产名称 ASSET_NAME LIKE
就是很尴尬,我们没有办法控制客户使用的条件,就我们常用的三个基础条件来说,period_counter_fully_retired、FA_BOOK_CODE、PERIOD_NAME,
省份表中PERIOD_NAME基本上都是同一个区间的数据,无法区分任何数据,同一个省份表中FA_BOOK_CODE,也是全量信息,也是无法区分任何一个数据,唯一能够区分的数据就是说period_counter_fully_retired,大概可以区分1/3 的数据,而且我们的一个省份表表数据量在几亿到几万之间,所以说我们一定要创建索引。
办法
之前想了一个办法使用FORCE INDEX (idx_asset_book_period) 这个强制使用某一个索引,这是其中的一个方法,但是实话说用处不大,我们为了更大的效率一定要使用索引选择器的,我们一定要让索引选择器主动使用索引选择器。
实话说我想了,一些很有意思的想法,我们还是使用强制索引,然后根据不同的条件,输入不同的参数当做索引名称,相当于我们自己做了一套索引选择器,不过没有啥意义。
然后我们开始区分条件,首先是period_counter_fully_retired这个条件是可以使用的,然后就是常用的条件创建一次少字段的索引,这边暂时不讨论超过2个字段的联合索引,因为确实是感觉性价比不高。
sql
CREATE INDEX idx_retired_SEG1 ON cams_asset_detail_ha_202606
(period_counter_fully_retired,FA_CATE_SEG1_CODE);
CREATE INDEX idx_retired_SEG2 ON cams_asset_detail_ha_202606
(period_counter_fully_retired,FA_CATE_SEG2_CODE);
CREATE INDEX idx_retired_SEG3 ON cams_asset_detail_ha_202606
(period_counter_fully_retired,FA_CATE_SEG3_CODE);
CREATE INDEX idx_retired_CREATION ON cams_asset_detail_ha_202606
(period_counter_fully_retired,CREATION_DATE);
CREATE INDEX idx_retired_ATTRIBUTE5 ON cams_asset_detail_ha_202606
(period_counter_fully_retired,FA_ADD_ATTRIBUTE5);
CREATE INDEX idx_retired_COST ON cams_asset_detail_ha_202606
(period_counter_fully_retired,COST);
CREATE INDEX idx_retired_TAG ON cams_asset_detail_ha_202606
(period_counter_fully_retired,TAG_NUMBER);
CREATE INDEX idx_retired_EMPLOYEE ON cams_asset_detail_ha_202606
(period_counter_fully_retired,EMPLOYEE_NUMBER);
CREATE INDEX idx_retired_JZZB ON cams_asset_detail_ha_202606
(period_counter_fully_retired,JZZB_DEPRN_RESERVE);
这是暂时可以使用的,使用了索引的查询条件的创建的索引,然后再次去explain之后,果然是走了我们的索引,然后又出现了一个尴尬的问题,有效果但是效果微乎其微,但是我是真真的没办法了,然后我巧了,去生产创建了索引,一测试,不用索引大概一个表2亿条数据,跑出来大概是一个小时左右,跑了索引大概是10分钟,实话说,大概就是到这里了,就这里了