• SELECT后面有自定义函数的优化方法


    又是EBS,一个SQL有700多行,要跑2个多小时,EBS开发请求帮忙优化
    因为SQL太长,将其做了简化处理,简化后的SQL文本如下:

    1. INSERT INTO nso_allocaton_fr_tmp
    2. (create_date,
    3. request_id,
    4. c1,
    5. c2,
    6. c3,
    7. c4,
    8. c5,
    9. c6,
    10. c7,
    11. c8,
    12. c9,
    13. c10,
    14. c11,
    15. c12,
    16. c13,
    17. c14,
    18. c15,
    19. c16,
    20. c17,
    21. c18,
    22. c19,
    23. c20,
    24. c21,
    25. c22,
    26. c23,
    27. c24,
    28. c25,
    29. c26,
    30. c27,
    31. c28,
    32. c29,
    33. c30,
    34. c31,
    35. c32,
    36. c33,
    37. c34,
    38. c35,
    39. c36,
    40. c37,
    41. c38,
    42. c39,
    43. c40,
    44. c41,
    45. c42,
    46. c43,
    47. c44,
    48. c45,
    49. c46,
    50. c47,
    51. c48,
    52. c49,
    53. c50,
    54. c51,
    55. c52,
    56. c53,
    57. c54,
    58. c55,
    59. c56,
    60. c57,
    61. c58,
    62. c59,
    63. c60,
    64. c61,
    65. c62,
    66. c63,
    67. c64,
    68. c65,
    69. c66,
    70. c67,
    71. c68,
    72. c70)
    73. (SELECT SYSDATE,
    74. fnd_global.conc_request_id,
    75. t.ou_name,
    76. t.ou_name,
    77. t.department_name,
    78. t.sales_person,
    79. t.project_number,
    80. t.item_category,
    81. (SELECT ffv.description
    82. FROM fnd_flex_value_sets ffvs, fnd_flex_values_vl ffv
    83. WHERE ffvs.flex_value_set_id = ffv.flex_value_set_id
    84. AND ffvs.flex_value_set_name = 'XY_COA_PD'
    85. AND ffv.flex_value = t.item_category
    86. AND ffv.enabled_flag = 'Y'
    87. AND nvl(ffv.start_date_active, trunc(SYSDATE)) <=
    88. trunc(SYSDATE)
    89. AND nvl(ffv.end_date_active, trunc(SYSDATE)) >= trunc(SYSDATE)
    90. AND rownum = 1) item_category_desc,
    91. t.item_type,
    92. t.trade_mode,
    93. t.po_type,
    94. t.so_area,
    95. t.so_type,
    96. DECODE('NET',
    97. 'TOTAL',
    98. DECODE(SUBSTR(t.segment3, 1, 8),
    99. '60010118',
    100. NULL,
    101. t.so_quantity),
    102. t.so_quantity),
    103. DECODE('NET',
    104. 'TOTAL',
    105. DECODE(SUBSTR(t.segment3, 1, 8),
    106. '60010118',
    107. NULL,
    108. t.internal_so_quantity),
    109. t.internal_so_quantity),
    110. DECODE('NET',
    111. 'TOTAL',
    112. DECODE(SUBSTR(t.segment3, 1, 8),
    113. '60010118',
    114. NULL,
    115. t.actual_so_quantity),
    116. t.actual_so_quantity),
    117. DECODE('NET',
    118. 'TOTAL',
    119. DECODE(SUBSTR(t.segment3, 1, 8),
    120. '60010118',
    121. NULL,
    122. t.so_revenue),
    123. t.so_revenue),
    124. DECODE('NET',
    125. 'TOTAL',
    126. DECODE(SUBSTR(t.segment3, 1, 8),
    127. '60010118',
    128. NULL,
    129. t.internal_so_revenue),
    130. t.internal_so_revenue),
    131. DECODE('NET',
    132. 'TOTAL',
    133. DECODE(SUBSTR(t.segment3, 1, 8),
    134. '60010118',
    135. NULL,
    136. t.actual_so_revenue),
    137. t.actual_so_revenue),
    138. DECODE('NET',
    139. 'TOTAL',
    140. DECODE(SUBSTR(t.segment3, 1, 8),
    141. '64010102',
    142. NULL,
    143. t.so_cost),
    144. t.so_cost),
    145. DECODE('NET',
    146. 'TOTAL',
    147. DECODE(SUBSTR(t.segment3, 1, 8),
    148. '64010102',
    149. NULL,
    150. t.internal_so_cost),
    151. t.internal_so_cost),
    152. t.so_cost_diff,
    153. (decode(t.actual_so_revenue,
    154. 0,
    155. NULL,
    156. NULL,
    157. NULL,
    158. round(100 *
    159. (nvl(t.so_cost_diff, 0) - nvl(t.expense_amount, 0)) /
    160. t.actual_so_revenue,
    161. 2))),
    162. t.expense_amount,
    163. t.margin_amount,
    164. (decode(t.actual_so_revenue,
    165. 0,
    166. NULL,
    167. NULL,
    168. NULL,
    169. round(100 *
    170. (nvl(t.so_cost_diff, 0) - nvl(t.expense_amount, 0)) /
    171. t.actual_so_revenue,
    172. 2))),
    173. abs(decode(t.actual_so_revenue,
    174. 0,
    175. NULL,
    176. NULL,
    177. NULL,
    178. round(100 * t.expense_amount / t.actual_so_revenue, 2))),
    179. t.so_number,
    180. t.so_line_num,
    181. t.ar_trx_number,
    182. t.ar_trx_line_num,
    183. t.gl_je_name,
    184. t.gl_je_line_num,
    185. t.lot_number,
    186. t.inv_organization_name,
    187. t.inv_item_number,
    188. t.inv_item_description,
    189. t.plan_po_number,
    190. t.plan_po_line_number,
    191. t.po_number,
    192. t.po_line_number,
    193. t.ap_invoice_number,
    194. t.ap_invoice_line_num,
    195. t.ra_trx_type_name,
    196. t.so_party_name,
    197. t.po_vendor_name,
    198. t.so_name,
    199. t.po_name,
    200. to_char(t.trans_date, 'YYYY-MM-DD HH24:MI:SS'),
    201. t.po_type1,
    202. t.comments,
    203. t.sales_type,
    204. t.primary_uom_code,
    205. t.project_number_desc,
    206. t.sales_percentage_rate,
    207. t.summary,
    208. t.so_country_name,
    209. t.rec_method,
    210. t.blanket_number,
    211. t.source_type,
    212. nvl(t.so_cost, 0) - nvl(t.purchase_cost, 0) -
    213. nvl(t.sales_cost, 0),
    214. t.purchase_cost,
    215. t.sales_cost,
    216. t.innerordercode,
    217. t.parent_project_number,
    218. nar_ar14_analysis_extract_pkg.get_out_contract_number(t.plan_po_number,
    219. t.project_number,
    220. t.org_id)
    221. ,DECODE(SUBSTR(t.segment3, 1, 8),
    222. '60010118',
    223. DECODE((SELECT COUNT(*)
    224. FROM gl_je_headers gjh,
    225. gl_je_sources_tl gs
    226. WHERE gjh.je_header_id = t.je_header_id
    227. AND gjh.je_source = gs.je_source_name
    228. AND gs.language = 'ZHS'
    229. AND gs.user_je_source_name IN
    230. ('人工', '自动复制')),
    231. 0,
    232. NULL,
    233. (SELECT TO_NUMBER(gjl.attribute5) *
    234. DECODE(nvl(nvl(gjl.accounted_dr,
    235. gjl.entered_dr),
    236. 0),
    237. 0,
    238. -1,
    239. 1)
    240. FROM gl_je_lines gjl
    241. WHERE gjl.je_header_id = t.je_header_id
    242. AND gjl.je_line_num = t.gl_je_line_num)),
    243. NULL),
    244. DECODE(SUBSTR(t.segment3, 1, 8),
    245. '60010118',
    246. DECODE((SELECT COUNT(*)
    247. FROM gl_je_headers gjh,
    248. gl_je_sources_tl gs
    249. WHERE gjh.je_header_id = t.je_header_id
    250. AND gjh.je_source = gs.je_source_name
    251. AND gs.language = 'ZHS'
    252. AND gs.user_je_source_name IN
    253. ('人工', '自动复制')
    254. ),
    255. 0,
    256. NULL,
    257. (SELECT nvl(gjl.accounted_cr, 0) -
    258. nvl(gjl.accounted_dr, 0)
    259. FROM gl_je_lines gjl
    260. WHERE gjl.je_header_id = t.je_header_id
    261. AND gjl.je_line_num = t.gl_je_line_num)),
    262. NULL),
    263. DECODE(SUBSTR(t.segment3, 1, 8),
    264. '64010102',
    265. DECODE((SELECT COUNT(*)
    266. FROM gl_je_headers gjh,
    267. gl_je_sources_tl gs
    268. WHERE gjh.je_header_id = t.je_header_id
    269. AND gjh.je_source = gs.je_source_name
    270. AND gs.language = 'ZHS'
    271. AND gs.user_je_source_name IN
    272. ('人工', '自动复制')),
    273. 0,
    274. NULL,
    275. (SELECT nvl(gjl.accounted_dr, 0) -
    276. nvl(gjl.accounted_cr, 0)
    277. FROM gl_je_lines gjl
    278. WHERE gjl.je_header_id = t.je_header_id
    279. AND gjl.je_line_num = t.gl_je_line_num)),
    280. NULL), 'RST' FROM x t ---x原本是一个超大的SELECT语句,它没有性能瓶颈,为了简化SQL,创建临时表x代替
    281. );

    x一共有4768314行数据,没有过滤条件

    SQL> select count(*) from x;
      COUNT(*)
    ----------
       4768314

    SQL里面有一堆标量子查询和自定义函数,肯定是标量子查询或者自定义函数影响了SQL的性能
    可以把SQL拆了,注释掉自定义函数或者注释掉标量子查询,再跑SQL看谁影响了SQL性能  
    最终定位到是自定义函数拖慢了整个SQL

    nar_ar14_analysis_extract_pkg.get_out_contract_number(t.plan_po_number,
                                                          t.project_number,
                                                          t.org_id)
    自定义函数的代码如下:

    1. FUNCTION get_out_contract_number(p_plan_po_number IN VARCHAR2
    2. ,p_project_number IN VARCHAR2
    3. ,p_org_id IN NUMBER) RETURN VARCHAR2 IS
    4. l_return VARCHAR2(240);
    5. BEGIN
    6. IF p_plan_po_number IS NOT NULL THEN
    7. SELECT pha.attribute1
    8. INTO l_return
    9. FROM po_headers_all pha
    10. WHERE pha.type_lookup_code = 'PLANNED'
    11. AND pha.attribute_category = 'CONTRACT'
    12. AND pha.segment1 = p_plan_po_number
    13. AND pha.org_id = p_org_id
    14. AND pha.attribute1 IS NOT NULL
    15. AND ROWNUM = 1;
    16. ELSIF p_project_number IS NOT NULL THEN
    17. SELECT pha.attribute1
    18. INTO l_return
    19. FROM po_distributions_all pda
    20. ,po_headers_all pha
    21. ,pa_projects_all ppa
    22. WHERE pha.po_header_id = pda.po_header_id
    23. AND pda.project_id = ppa.project_id
    24. AND pha.type_lookup_code = 'PLANNED'
    25. AND pha.attribute_category = 'CONTRACT'
    26. AND ppa.segment1 = p_project_number
    27. AND rownum = 1;
    28. ELSE
    29. l_return := NULL;
    30. END IF;
    31. RETURN l_return;
    32. EXCEPTION
    33. WHEN OTHERS THEN
    34. RETURN NULL;
    35. END get_out_contract_number;

    通过监控ASH,也能定位到是自定义函数拖慢了整个SQL

    1. SQL> select sql_id, sql_child_number, event, top_level_sql_id, count(*)
    2. 2 from gv$active_session_history
    3. 3 where session_id = 937
    4. 4 and sample_time >
    5. 5 to_date('2022-12-01 14:18:00', 'yyyy-mm-dd hh24:mi:ss')
    6. 6 group by sql_id, sql_child_number, event, top_level_sql_id
    7. 7 order by 5 desc;
    8. SQL_ID SQL_CHILD_NUMBER EVENT TOP_LEVEL_SQL_ID COUNT(*)
    9. ------------- ---------------- ------------------------------- ---------------- ----------
    10. 16j3dr5ws843j 5 1upgmu861mtaz 3110
    11. 1upgmu861mtaz 0 1upgmu861mtaz 55
    12. -1 1upgmu861mtaz 3
    13. 1upgmu861mtaz 0 db file sequential read 1upgmu861mtaz 2
    14. 0 1
    15. 264xbz7w614gx 5 1upgmu861mtaz 1
    16. SQL> select sql_text from v$sql where sql_id='16j3dr5ws843j' and rownum=1;
    17. SQL_TEXT
    18. --------------------------------------------------------------------------------
    19. SELECT PHA.ATTRIBUTE1 FROM PO_DISTRIBUTIONS_ALL PDA ,PO_HEADERS_ALL PHA ,PA_PROJ
    20. SQL> select sql_text from v$sql where sql_id='1upgmu861mtaz' and rownum=1;
    21. SQL_TEXT
    22. --------------------------------------------------------------------------------
    23. INSERT INTO nso_allocaton_fr_tmp (create_date, request_id, c1, c2,

    定位到是因为自定义函数部分拖慢了性能就好办了

    优化方法一:

    将自定义函数的逻辑改写为标量子查询(为了方便读者阅读,进一步简化了SQL,省略了其他列)

    1. SQL> INSERT INTO nso_allocaton_fr_tmp
    2. 2 (c65)
    3. 3 (SELECT
    4. 4 case
    5. 5 when plan_po_number IS NOT NULL THEN
    6. 6 (SELECT pha.attribute1
    7. 7 FROM po_headers_all pha
    8. 8 WHERE pha.type_lookup_code = 'PLANNED'
    9. 9 AND pha.attribute_category = 'CONTRACT'
    10. 10 AND pha.segment1 = t.plan_po_number
    11. 11 AND pha.org_id = t.org_id
    12. 12 AND pha.attribute1 IS NOT NULL
    13. 13 AND ROWNUM=1
    14. 14 )
    15. 15 when project_number IS NOT NULL THEN
    16. 16 (SELECT pha.attribute1
    17. 17 FROM po_distributions_all pda,
    18. 18 po_headers_all pha,
    19. 19 pa_projects_all ppa
    20. 20 WHERE pha.po_header_id = pda.po_header_id
    21. 21 AND pda.project_id = ppa.project_id
    22. 22 AND pha.type_lookup_code = 'PLANNED'
    23. 23 AND pha.attribute_category = 'CONTRACT'
    24. 24 AND ppa.segment1 = t.project_number
    25. 25 AND ROWNUM=1
    26. 26 )
    27. 27 else null end FROM x t
    28. 28 );
    29. 4768314 rows inserted
    30. Executed in 21.315 seconds

    优化方法二:

    不改写自定义函数逻辑,把对自定义函数的直接访问改成标量子查询+dual来访问

    1. SQL> INSERT INTO nso_allocaton_fr_tmp
    2. 2 (c65)
    3. 3 (SELECT (select nar_ar14_analysis_extract_pkg.get_out_contract_number(t.plan_po_number,
    4. 4 t.project_number,
    5. 5 t.org_id)
    6. 6 from dual)
    7. 7 FROM x t);
    8. 4768314 rows inserted
    9. Executed in 25.836 seconds

    优化方法一跑了21秒,优化方法二跑了25秒,优化方法二多出来的4秒是SQL引擎和PLSQL引擎上下文切换引起的

    读者可能会有疑问,为什么将SQL

    INSERT INTO nso_allocaton_fr_tmp
      (c65)
      (SELECT nar_ar14_analysis_extract_pkg.get_out_contract_number(t.plan_po_number,
                                                                    t.project_number,
                                                                    t.org_id)
         FROM x t);

    改成

    INSERT INTO nso_allocaton_fr_tmp
      (c65)
      (SELECT (select nar_ar14_analysis_extract_pkg.get_out_contract_number(t.plan_po_number,
                                                                            t.project_number,
                                                                            t.org_id)
                 from dual)
         FROM x t);

    就能从2个多小时优化到几十秒呢?

    那是因为没有改写之前,函数nar_ar14_analysis_extract_pkg.get_out_contract_number要被调用4768314次
    而改写之后,函数nar_ar14_analysis_extract_pkg.get_out_contract_number只会被调用2485次(标量子查询特性)

    SQL> select count(*)
      2    from ( 
      3           select t.plan_po_number, t.project_number, t.org_id
      4             from x t
      5            group by t.plan_po_number, t.project_number, t.org_id);
      
      COUNT(*)
    ----------
          2485
          
    如果函数传参的列是主键,那么前面两种优化方法都不对,需要将函数的SQL改写为LEFT JOIN,这里就不做展示了

    各位读者,如果以后遇到了自定义函数引起SQL慢,自定义函数是直接访问(没有通过select...dual访问)
    自定义函数逻辑非常复杂(改写困难),传参列distinct值少,可以采用本案例第二种优化方法试一试      

  • 相关阅读:
    Grafana + Prometheus监控篇之Windows监控Linux服务器资源
    html和css基础练习
    对比 Axios 和 Fetch:选择最适合的 HTTP 请求方法
    神机百炼3.52-Prim
    Simple Context Menu
    Flask 用户登录,表单提交
    为什么Python是数据科学家的首选语言
    Bean的循环依赖问题
    Git命令总结
    SaaSBase:什么是易客管家SCRM?
  • 原文地址:https://blog.csdn.net/robinson1988/article/details/128208864