大数据量导出 优化方案多版 第五版

需求

本来是在完成一个需求,资产转派,老麻烦了,然后还没有开始做,然后就来了一个需求,说是导出优化,对了,就是那个优化,之前就是困扰我的问题,现在更换了数据库了,从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分钟,实话说,大概就是到这里了,就这里了

相关推荐
Data_Journal1 小时前
如何使用 Python 抓取 Google Flights:分步指南
大数据·开发语言·数据库·python·scrapy
深藏bIue1 小时前
PostgreSQL 冻结与死元组回收
数据库·postgresql
滨哥GPT1 小时前
Codex修改数据库后项目启动失败怎么办?Migration、Schema与数据结构排查
数据库·ai编程·开发工具·schema·codex
戴西软件1 小时前
国内有哪些数据轻量化格式转换软件?
数据库·算法·安全·信息可视化·自动化·rpa
λqaq72 小时前
MySQL 数据库基础(2)约束、用户权限、远程连接、外键与 TCP/UDP 理解
linux·数据库·python·tcp/ip·mysql
丰锋ff2 小时前
基于C++11的数据库连接池
数据库
TDengine (老段)2 小时前
TDengine 应用案例 — 工业大数据与智能制造
大数据·数据库·制造·时序数据库·tdengine·涛思数据
狂热开发者2 小时前
打印机卖到欧洲以后 App 更新了,旧包装二维码还能用吗
数据库
张3蜂2 小时前
2026 中国国产数据库 Top 20:市场格局、开源协议与竞品分析
数据库