未记录事实核验 · 状态:维护中

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

warning 适用边界与维护说明

本文适合具备Oracle SQL、PL/SQL及执行计划基础的读者讨论银行业务调优方案,不是可直接部署或已验证性能收益的脚本集;本次仅作全文结构审阅,未作外部事实核验或数据库运行测试。正文未提供业务表DDL、样例数据及基准执行计划,多组候选查询已明确改变业务口径,比较性能前须先确认结果一致。执行计划分析函数、原始交易统计中的DATE_TRUNC、快速刷新物化视图及调优任务名称长度仍有具体实现问题。自动索引建议仅是简化文本匹配,SQL Profile建议函数是占位实现;滚动窗口按记录数计算,V$SQL监控反映游标累计统计,不能作为连续日历窗口、业务响应时间或可靠小时吞吐量。运行诊断、调优任务和定时采集前,应确认目标Oracle版本、直接授权及相关功能许可,并以目标环境的实际计划和测量结果评估效果。

Oracle SQL调优实战

SQL调优基础概念

SQL调优是提升Oracle数据库性能的核心技能,特别在银行系统中,良好的SQL性能直接影响客户体验和业务处理效率。SQL调优不仅涉及语句重写,还包括索引优化、统计信息管理和执行计划分析。

1. SQL调优的基本原则

减少数据访问量

  • 只查询需要的列和行
  • 使用WHERE条件过滤数据
  • 避免不必要的排序和分组

优化连接操作

  • 选择合适的连接类型
  • 根据连接方式、选择性及实际执行计划评估是否为连接列建立索引
  • 控制连接顺序

充分利用索引

  • 根据过滤选择性、排序与分组需求、组合索引顺序及写入维护成本评估索引,不必为所有相关列建立索引
  • 避免在索引列上使用函数
  • 合理使用复合索引

减少IO操作

  • 使用绑定变量
  • 合理使用HINT
  • 优化执行计划

执行计划分析

1. 查看和分析执行计划

-- 查看SQL执行计划的几种方法

-- 方法1:使用EXPLAIN PLAN
EXPLAIN PLAN FOR
SELECT c.customer_name, a.account_id, a.balance
FROM customer_info c
JOIN account_info a ON c.customer_id = a.customer_id
WHERE c.customer_grade = 'GOLD'
AND a.balance > 100000;

-- 查看执行计划
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

-- 方法2:使用DBMS_XPLAN.DISPLAY_CURSOR(实际执行计划)
SELECT /*+ GATHER_PLAN_STATISTICS */ 
    c.customer_name, a.account_id, a.balance
FROM customer_info c
JOIN account_info a ON c.customer_id = a.customer_id
WHERE c.customer_grade = 'GOLD'
AND a.balance > 100000;

-- 查看实际执行计划
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST'));

-- 方法3:使用SQL Developer或Enterprise Manager的图形化界面

2. 执行计划关键信息解读

-- 创建执行计划分析函数
CREATE OR REPLACE FUNCTION analyze_execution_plan(
    p_sql_id VARCHAR2
) RETURN CLOB AS
    v_plan_output CLOB;
    v_analysis CLOB;
    v_cost NUMBER;
    v_rows NUMBER;
    v_time NUMBER;
BEGIN
    -- 获取执行计划
    SELECT plan_table_output
    INTO v_plan_output
    FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(p_sql_id, NULL, 'ADVANCED'));
    
    -- 获取基本统计信息
    SELECT cost, cardinality, elapsed_time
    INTO v_cost, v_rows, v_time
    FROM v$sql
    WHERE sql_id = p_sql_id
    AND ROWNUM = 1;
    
    v_analysis := '=== SQL执行计划分析 ===' || CHR(10) ||
                  'SQL ID: ' || p_sql_id || CHR(10) ||
                  '成本: ' || v_cost || CHR(10) ||
                  '预估行数: ' || v_rows || CHR(10) ||
                  '执行时间: ' || ROUND(v_time/1000000, 2) || ' 秒' || CHR(10) || CHR(10) ||
                  '=== 详细执行计划 ===' || CHR(10) ||
                  v_plan_output || CHR(10) || CHR(10);
    
    -- 添加优化建议
    v_analysis := v_analysis || '=== 优化建议 ===' || CHR(10);
    
    IF v_cost > 1000 THEN
        v_analysis := v_analysis || '• 成本较高,检查是否缺少索引' || CHR(10);
    END IF;
    
    IF v_time > 5000000 THEN -- 超过5秒
        v_analysis := v_analysis || '• 执行时间过长,考虑重写SQL或优化索引' || CHR(10);
    END IF;
    
    RETURN v_analysis;
    
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RETURN 'SQL ID ' || p_sql_id || ' 未找到';
    WHEN OTHERS THEN
        RETURN '分析执行计划时出错: ' || SQLERRM;
END analyze_execution_plan;
/

银行业务SQL调优实例

1. 客户查询优化

-- 原始业务查询示例:效率需结合实际执行计划和测量判断
SELECT c.customer_id, c.customer_name, c.phone, c.email,
       a.account_id, a.balance, a.account_type,
       t.trans_count, t.total_amount
FROM customer_info c
LEFT JOIN account_info a ON c.customer_id = a.customer_id
LEFT JOIN (
    SELECT account_id, 
           COUNT(*) as trans_count,
           SUM(amount) as total_amount
    FROM transaction_log
    WHERE trans_date >= TRUNC(SYSDATE) - 30
    GROUP BY account_id
) t ON a.account_id = t.account_id
WHERE c.customer_grade IN ('GOLD', 'PLATINUM', 'DIAMOND')
ORDER BY c.customer_name;

-- 候选查询:添加索引提示并改变日期、状态及NULL口径,不是原查询的等价重写
SELECT /*+ INDEX(c idx_customer_grade)
           INDEX(a idx_account_customer) */
       c.customer_id, c.customer_name, c.phone, c.email,
       a.account_id, a.balance, a.account_type,
       NVL(t.trans_count, 0) as trans_count,
       NVL(t.total_amount, 0) as total_amount
FROM customer_info c
LEFT JOIN account_info a ON c.customer_id = a.customer_id
LEFT JOIN (
    SELECT account_id, 
           COUNT(*) as trans_count,
           SUM(amount) as total_amount
    FROM transaction_log
    WHERE trans_date BETWEEN TRUNC(SYSDATE) - 30 AND TRUNC(SYSDATE)
    AND status = 'SUCCESS'  -- 添加状态过滤
    GROUP BY account_id
) t ON a.account_id = t.account_id
WHERE c.customer_grade IN ('GOLD', 'PLATINUM', 'DIAMOND')
AND c.status = 'ACTIVE'  -- 添加客户状态过滤
ORDER BY c.customer_name;

-- 创建必要的索引
CREATE INDEX idx_customer_grade ON customer_info(customer_grade, status);
CREATE INDEX idx_account_customer ON account_info(customer_id, status);
CREATE INDEX idx_trans_date_account ON transaction_log(trans_date, account_id, status);

2. 交易统计查询优化

-- 原始交易统计示例:访问路径与性能需由实际执行计划和测量确认
SELECT DATE_TRUNC('month', trans_date) as month_year,
       trans_type,
       COUNT(*) as trans_count,
       SUM(amount) as total_amount,
       AVG(amount) as avg_amount
FROM transaction_log
WHERE trans_date >= DATE '2023-01-01'
GROUP BY DATE_TRUNC('month', trans_date), trans_type
ORDER BY month_year, trans_type;

-- 候选交易统计查询:使用并行提示并改变日期、状态和均值口径;本查询未展示分区定义或读取预计算结果
SELECT /*+ PARALLEL(tl, 4) */
       TRUNC(trans_date, 'MM') as month_year,
       trans_type,
       COUNT(*) as trans_count,
       SUM(amount) as total_amount,
       ROUND(AVG(amount), 2) as avg_amount
FROM transaction_log tl
WHERE trans_date >= DATE '2023-01-01'
AND trans_date < DATE '2024-01-01'  -- 使用范围查询
AND status = 'SUCCESS'  -- 只统计成功交易
GROUP BY TRUNC(trans_date, 'MM'), trans_type
ORDER BY month_year, trans_type;

-- 创建月度统计物化视图
CREATE MATERIALIZED VIEW mv_monthly_transaction_stats
BUILD IMMEDIATE
REFRESH FAST ON DEMAND
AS
SELECT TRUNC(trans_date, 'MM') as month_year,
       trans_type,
       COUNT(*) as trans_count,
       SUM(amount) as total_amount,
       ROUND(AVG(amount), 2) as avg_amount,
       COUNT(*) as mv_count  -- 用于快速刷新
FROM transaction_log
WHERE status = 'SUCCESS'
GROUP BY TRUNC(trans_date, 'MM'), trans_type;

-- 创建物化视图日志以支持快速刷新
CREATE MATERIALIZED VIEW LOG ON transaction_log
WITH ROWID, SEQUENCE(trans_date, trans_type, amount, status)
INCLUDING NEW VALUES;

3. 复杂报表查询优化

-- 银行客户价值分析报表优化
-- 原始报表查询:包含多次相关子查询,效率需实测
SELECT c.customer_id,
       c.customer_name,
       c.customer_grade,
       (SELECT SUM(balance) FROM account_info WHERE customer_id = c.customer_id) as total_balance,
       (SELECT COUNT(*) FROM account_info WHERE customer_id = c.customer_id) as account_count,
       (SELECT COUNT(*) FROM transaction_log tl 
        JOIN account_info ai ON tl.account_id = ai.account_id 
        WHERE ai.customer_id = c.customer_id 
        AND tl.trans_date >= TRUNC(SYSDATE) - 365) as yearly_trans_count,
       (SELECT SUM(amount) FROM transaction_log tl 
        JOIN account_info ai ON tl.account_id = ai.account_id 
        WHERE ai.customer_id = c.customer_id 
        AND tl.trans_date >= TRUNC(SYSDATE) - 365
        AND tl.trans_type = 'DEPOSIT') as yearly_deposit_amount
FROM customer_info c
WHERE c.status = 'ACTIVE'
ORDER BY total_balance DESC;

-- 候选报表查询:使用预聚合和JOIN,同时改变账户状态、交易状态、NULL及排序口径,不是等价重写
WITH customer_accounts AS (
    SELECT customer_id,
           SUM(balance) as total_balance,
           COUNT(*) as account_count
    FROM account_info
    WHERE status = 'ACTIVE'
    GROUP BY customer_id
),
customer_transactions AS (
    SELECT ai.customer_id,
           COUNT(*) as yearly_trans_count,
           SUM(CASE WHEN tl.trans_type = 'DEPOSIT' THEN tl.amount ELSE 0 END) as yearly_deposit_amount
    FROM transaction_log tl
    JOIN account_info ai ON tl.account_id = ai.account_id
    WHERE tl.trans_date >= TRUNC(SYSDATE) - 365
    AND tl.status = 'SUCCESS'
    AND ai.status = 'ACTIVE'
    GROUP BY ai.customer_id
)
SELECT c.customer_id,
       c.customer_name,
       c.customer_grade,
       NVL(ca.total_balance, 0) as total_balance,
       NVL(ca.account_count, 0) as account_count,
       NVL(ct.yearly_trans_count, 0) as yearly_trans_count,
       NVL(ct.yearly_deposit_amount, 0) as yearly_deposit_amount
FROM customer_info c
LEFT JOIN customer_accounts ca ON c.customer_id = ca.customer_id
LEFT JOIN customer_transactions ct ON c.customer_id = ct.customer_id
WHERE c.status = 'ACTIVE'
ORDER BY ca.total_balance DESC NULLS LAST;

SQL重写技巧

1. 条件优化技巧

-- 避免在WHERE子句中使用函数
-- 错误示例
SELECT * FROM transaction_log 
WHERE TO_CHAR(trans_date, 'YYYY-MM') = '2023-12';

-- 正确示例
SELECT * FROM transaction_log 
WHERE trans_date >= DATE '2023-12-01' 
AND trans_date < DATE '2024-01-01';

-- 对比IN与EXISTS的等价写法;性能取决于实际执行计划和数据分布
-- IN查询示例,效率需实测
SELECT * FROM customer_info 
WHERE customer_id IN (
    SELECT customer_id FROM account_info WHERE balance > 1000000
);

-- EXISTS查询示例,效率需实测
SELECT * FROM customer_info c
WHERE EXISTS (
    SELECT 1 FROM account_info a 
    WHERE a.customer_id = c.customer_id 
    AND a.balance > 1000000
);

-- 使用UNION ALL替代UNION(当确定没有重复数据时)
SELECT customer_id, 'HIGH_VALUE' as type FROM customer_info WHERE customer_grade = 'DIAMOND'
UNION ALL
SELECT customer_id, 'MEDIUM_VALUE' as type FROM customer_info WHERE customer_grade = 'GOLD'
UNION ALL
SELECT customer_id, 'LOW_VALUE' as type FROM customer_info WHERE customer_grade = 'SILVER';

2. 连接优化技巧

-- 银行账户余额对账优化
-- 原始SQL:使用逗号连接,已提供两组连接条件,并非笛卡尔积
SELECT c.customer_name, a.account_id, a.balance, 
       SUM(CASE WHEN t.trans_type IN ('DEPOSIT', 'TRANSFER_IN') THEN t.amount ELSE -t.amount END) as calculated_balance
FROM customer_info c, account_info a, transaction_log t
WHERE c.customer_id = a.customer_id
AND a.account_id = t.account_id
GROUP BY c.customer_name, a.account_id, a.balance;

-- 候选对账查询:明确连接并新增过滤,同时改变交易类型处理和无交易账户返回规则,不是等价重写
SELECT c.customer_name, 
       a.account_id, 
       a.balance,
       NVL(calc.calculated_balance, 0) as calculated_balance,
       a.balance - NVL(calc.calculated_balance, 0) as difference
FROM customer_info c
JOIN account_info a ON c.customer_id = a.customer_id
LEFT JOIN (
    SELECT account_id,
           SUM(CASE 
               WHEN trans_type IN ('DEPOSIT', 'TRANSFER_IN') THEN amount 
               WHEN trans_type IN ('WITHDRAW', 'TRANSFER_OUT') THEN -amount 
               ELSE 0 
           END) as calculated_balance
    FROM transaction_log
    WHERE status = 'SUCCESS'
    GROUP BY account_id
) calc ON a.account_id = calc.account_id
WHERE c.status = 'ACTIVE'
AND a.status = 'ACTIVE';

高级调优技术

1. 分析函数优化

-- 使用分析函数优化排名查询
-- 银行客户资产排名
SELECT customer_id,
       customer_name,
       total_assets,
       RANK() OVER (ORDER BY total_assets DESC) as asset_rank,
       NTILE(10) OVER (ORDER BY total_assets DESC) as asset_decile,
       LAG(total_assets) OVER (ORDER BY total_assets DESC) as prev_customer_assets,
       total_assets - LAG(total_assets) OVER (ORDER BY total_assets DESC) as asset_gap
FROM (
    SELECT c.customer_id,
           c.customer_name,
           SUM(a.balance) as total_assets
    FROM customer_info c
    JOIN account_info a ON c.customer_id = a.customer_id
    WHERE c.status = 'ACTIVE' AND a.status = 'ACTIVE'
    GROUP BY c.customer_id, c.customer_name
);

-- 使用分析函数计算移动平均
SELECT trans_date,
       daily_amount,
       AVG(daily_amount) OVER (
           ORDER BY trans_date 
           ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
       ) as moving_avg_7rows,
       SUM(daily_amount) OVER (
           ORDER BY trans_date 
           ROWS BETWEEN 29 PRECEDING AND CURRENT ROW
       ) as rolling_sum_30rows
FROM (
    SELECT TRUNC(trans_date) as trans_date,
           SUM(amount) as daily_amount
    FROM transaction_log
    WHERE trans_date >= TRUNC(SYSDATE) - 90
    AND status = 'SUCCESS'
    GROUP BY TRUNC(trans_date)
)
ORDER BY trans_date;

2. 绑定变量使用

-- 创建使用绑定变量的存储过程
CREATE OR REPLACE PROCEDURE get_customer_transactions(
    p_customer_id IN VARCHAR2,
    p_start_date IN DATE,
    p_end_date IN DATE,
    p_cursor OUT SYS_REFCURSOR
) AS
BEGIN
    OPEN p_cursor FOR
    SELECT t.trans_id,
           t.trans_date,
           t.trans_type,
           t.amount,
           t.description,
           a.account_id
    FROM transaction_log t
    JOIN account_info a ON t.account_id = a.account_id
    WHERE a.customer_id = p_customer_id
    AND t.trans_date BETWEEN p_start_date AND p_end_date
    AND t.status = 'SUCCESS'
    ORDER BY t.trans_date DESC;
END get_customer_transactions;
/

-- 反例:直接拼接表名和任意条件存在SQL注入风险,不能作为安全绑定变量方案
CREATE OR REPLACE FUNCTION build_dynamic_query(
    p_table_name VARCHAR2,
    p_conditions VARCHAR2
) RETURN VARCHAR2 AS
    v_sql VARCHAR2(4000);
BEGIN
    v_sql := 'SELECT * FROM ' || p_table_name || 
             ' WHERE 1=1 ' || p_conditions;
    RETURN v_sql;
END;
/

-- 正确的动态SQL使用方式
DECLARE
    v_sql VARCHAR2(1000);
    v_cursor SYS_REFCURSOR;
    v_customer_id VARCHAR2(20) := 'CUST001';
    v_start_date DATE := DATE '2023-01-01';
BEGIN
    v_sql := 'SELECT trans_id, amount FROM transaction_log t ' ||
             'JOIN account_info a ON t.account_id = a.account_id ' ||
             'WHERE a.customer_id = :1 AND t.trans_date >= :2';
    
    OPEN v_cursor FOR v_sql USING v_customer_id, v_start_date;
    -- 处理结果
    CLOSE v_cursor;
END;
/

SQL调优工具和监控

1. SQL调优包

-- SQL调优分析包
CREATE OR REPLACE PACKAGE pkg_sql_tuning AS
    
    PROCEDURE analyze_slow_sql(p_threshold_seconds NUMBER DEFAULT 5);
    PROCEDURE generate_tuning_report(p_sql_id VARCHAR2);
    PROCEDURE auto_create_index_suggestions;
    FUNCTION get_sql_profile_recommendations(p_sql_id VARCHAR2) RETURN CLOB;
    
END pkg_sql_tuning;
/

CREATE OR REPLACE PACKAGE BODY pkg_sql_tuning AS
    
    PROCEDURE analyze_slow_sql(p_threshold_seconds NUMBER DEFAULT 5) AS
    BEGIN
        DBMS_OUTPUT.PUT_LINE('慢SQL分析报告(执行时间 > ' || p_threshold_seconds || ' 秒)');
        DBMS_OUTPUT.PUT_LINE(RPAD('=', 80, '='));
        DBMS_OUTPUT.PUT_LINE(RPAD('SQL_ID', 15) || RPAD('执行次数', 10) || 
                            RPAD('平均耗时', 12) || RPAD('总耗时', 12) || '业务模块');
        DBMS_OUTPUT.PUT_LINE(RPAD('-', 80, '-'));
        
        FOR slow_sql IN (
            SELECT sql_id,
                   executions,
                   ROUND(elapsed_time / executions / 1000000, 2) as avg_seconds,
                   ROUND(elapsed_time / 1000000, 2) as total_seconds,
                   CASE 
                       WHEN UPPER(sql_text) LIKE '%CUSTOMER_INFO%' THEN 'Customer'
                       WHEN UPPER(sql_text) LIKE '%ACCOUNT_INFO%' THEN 'Account'
                       WHEN UPPER(sql_text) LIKE '%TRANSACTION_LOG%' THEN 'Transaction'
                       ELSE 'Other'
                   END as module
            FROM v$sql
            WHERE executions > 0
            AND elapsed_time / executions / 1000000 > p_threshold_seconds
            ORDER BY elapsed_time DESC
            FETCH FIRST 20 ROWS ONLY
        ) LOOP
            DBMS_OUTPUT.PUT_LINE(
                RPAD(slow_sql.sql_id, 15) ||
                RPAD(slow_sql.executions, 10) ||
                RPAD(slow_sql.avg_seconds, 12) ||
                RPAD(slow_sql.total_seconds, 12) ||
                slow_sql.module
            );
        END LOOP;
    END analyze_slow_sql;
    
    PROCEDURE generate_tuning_report(p_sql_id VARCHAR2) AS
        v_task_name VARCHAR2(30);
        v_report CLOB;
    BEGIN
        -- 创建SQL调优任务
        v_task_name := 'TUNE_' || p_sql_id || '_' || TO_CHAR(SYSDATE, 'YYYYMMDDHH24MI');
        
        v_task_name := DBMS_SQLTUNE.CREATE_TUNING_TASK(
            sql_id => p_sql_id,
            task_name => v_task_name
        );
        
        -- 执行调优任务
        DBMS_SQLTUNE.EXECUTE_TUNING_TASK(v_task_name);
        
        -- 获取调优报告
        v_report := DBMS_SQLTUNE.REPORT_TUNING_TASK(v_task_name);
        
        DBMS_OUTPUT.PUT_LINE('SQL调优报告:');
        DBMS_OUTPUT.PUT_LINE(v_report);
        
        -- 清理调优任务
        DBMS_SQLTUNE.DROP_TUNING_TASK(v_task_name);
        
    EXCEPTION
        WHEN OTHERS THEN
            DBMS_OUTPUT.PUT_LINE('生成调优报告失败: ' || SQLERRM);
    END generate_tuning_report;
    
    PROCEDURE auto_create_index_suggestions AS
        v_suggestion_count NUMBER := 0;
    BEGIN
        DBMS_OUTPUT.PUT_LINE('自动索引建议分析');
        DBMS_OUTPUT.PUT_LINE(RPAD('=', 60, '='));
        
        -- 分析缺少索引的WHERE条件
        FOR idx_suggestion IN (
            SELECT DISTINCT 
                   table_name,
                   column_name,
                   COUNT(*) as usage_count
            FROM (
                SELECT 
                    REGEXP_SUBSTR(UPPER(sql_text), 'FROM\s+(\w+)', 1, 1, NULL, 1) as table_name,
                    REGEXP_SUBSTR(UPPER(sql_text), 'WHERE\s+(\w+)', 1, 1, NULL, 1) as column_name
                FROM v$sql
                WHERE UPPER(sql_text) LIKE '%WHERE%'
                AND executions > 10
                AND elapsed_time / executions > 1000000 -- 平均执行时间超过1秒
            )
            WHERE table_name IS NOT NULL 
            AND column_name IS NOT NULL
            AND table_name IN ('CUSTOMER_INFO', 'ACCOUNT_INFO', 'TRANSACTION_LOG')
            GROUP BY table_name, column_name
            HAVING COUNT(*) >= 5 -- 至少匹配5条游标记录,不代表SQL执行次数
            ORDER BY usage_count DESC
        ) LOOP
            -- 检查索引是否已存在
            DECLARE
                v_index_exists NUMBER;
            BEGIN
                SELECT COUNT(*)
                INTO v_index_exists
                FROM user_ind_columns
                WHERE table_name = idx_suggestion.table_name
                AND column_name = idx_suggestion.column_name;
                
                IF v_index_exists = 0 THEN
                    DBMS_OUTPUT.PUT_LINE(
                        '建议创建索引: CREATE INDEX idx_' || 
                        LOWER(idx_suggestion.table_name) || '_' || 
                        LOWER(idx_suggestion.column_name) || 
                        ' ON ' || idx_suggestion.table_name || 
                        '(' || idx_suggestion.column_name || ');'
                    );
                    v_suggestion_count := v_suggestion_count + 1;
                END IF;
            END;
        END LOOP;
        
        DBMS_OUTPUT.PUT_LINE('');
        DBMS_OUTPUT.PUT_LINE('共生成 ' || v_suggestion_count || ' 个索引建议');
    END auto_create_index_suggestions;
    
    FUNCTION get_sql_profile_recommendations(p_sql_id VARCHAR2) RETURN CLOB AS
        v_recommendations CLOB;
    BEGIN
        v_recommendations := 'SQL Profile建议 (' || p_sql_id || ')' || CHR(10) || CHR(10);
        
        -- 这里可以添加具体的SQL Profile分析逻辑
        -- 基于执行计划、统计信息等给出建议
        
        v_recommendations := v_recommendations || 
            '1. 检查表统计信息是否最新' || CHR(10) ||
            '2. 考虑使用HINT强制优化器选择更好的执行计划' || CHR(10) ||
            '3. 评估是否需要创建SQL Profile辅助优化器估算;SQL Profile不保证固定执行计划' || CHR(10);
        
        RETURN v_recommendations;
    END get_sql_profile_recommendations;
    
END pkg_sql_tuning;
/

2. 自动化SQL监控

-- SQL性能监控表
CREATE TABLE sql_performance_history (
    capture_time DATE,
    sql_id VARCHAR2(13),
    plan_hash_value NUMBER,
    executions NUMBER,
    elapsed_time NUMBER,
    cpu_time NUMBER,
    buffer_gets NUMBER,
    disk_reads NUMBER,
    rows_processed NUMBER
);

-- 自动收集SQL性能数据的作业
CREATE OR REPLACE PROCEDURE collect_sql_performance AS
BEGIN
    INSERT INTO sql_performance_history
    SELECT SYSDATE,
           sql_id,
           plan_hash_value,
           executions,
           elapsed_time,
           cpu_time,
           buffer_gets,
           disk_reads,
           rows_processed
    FROM v$sql
    WHERE executions > 0
    AND elapsed_time > 0
    AND sql_text NOT LIKE '%sql_performance_history%'; -- 排除自身
    
    COMMIT;
    
    -- 清理30天前的数据
    DELETE FROM sql_performance_history 
    WHERE capture_time < SYSDATE - 30;
    
    COMMIT;
END collect_sql_performance;
/

-- 创建定时作业
BEGIN
    DBMS_SCHEDULER.CREATE_JOB (
        job_name => 'SQL_PERFORMANCE_COLLECTOR',
        job_type => 'PLSQL_BLOCK',
        job_action => 'BEGIN collect_sql_performance; END;',
        start_date => SYSTIMESTAMP,
        repeat_interval => 'FREQ=HOURLY; INTERVAL=1',
        enabled => TRUE,
        comments => '每小时收集SQL性能数据'
    );
END;
/

调优最佳实践

1. 银行业务SQL标准

-- 银行SQL编写规范示例
-- 1. 客户查询标准模板
CREATE OR REPLACE FUNCTION get_customer_summary_std(
    p_customer_id VARCHAR2
) RETURN SYS_REFCURSOR AS
    v_cursor SYS_REFCURSOR;
BEGIN
    OPEN v_cursor FOR
    SELECT /*+ FIRST_ROWS(1) */
           c.customer_id,
           c.customer_name,
           c.customer_grade,
           c.phone,
           c.email,
           COUNT(a.account_id) as account_count,
           SUM(a.balance) as total_balance
    FROM customer_info c
    LEFT JOIN account_info a ON c.customer_id = a.customer_id 
                            AND a.status = 'ACTIVE'
    WHERE c.customer_id = p_customer_id
    AND c.status = 'ACTIVE'
    GROUP BY c.customer_id, c.customer_name, c.customer_grade, c.phone, c.email;
    
    RETURN v_cursor;
END;
/

-- 2. 交易查询标准模板
CREATE OR REPLACE PROCEDURE get_transaction_history_std(
    p_account_id IN VARCHAR2,
    p_start_date IN DATE,
    p_end_date IN DATE,
    p_cursor OUT SYS_REFCURSOR
) AS
BEGIN
    OPEN p_cursor FOR
    SELECT /*+ INDEX(t idx_trans_account_date) */
           t.trans_id,
           t.trans_date,
           t.trans_type,
           t.amount,
           t.balance_after,
           t.description
    FROM transaction_log t
    WHERE t.account_id = p_account_id
    AND t.trans_date BETWEEN p_start_date AND p_end_date
    AND t.status = 'SUCCESS'
    ORDER BY t.trans_date DESC;
END;
/

2. 性能监控指标

-- 关键性能指标监控视图
CREATE OR REPLACE VIEW v_sql_performance_kpi AS
SELECT 
    'Unweighted mean of cursor cumulative average elapsed seconds' as metric_name,
    ROUND(AVG(elapsed_time / executions / 1000000), 3) as avg_value,
    ROUND(MAX(elapsed_time / executions / 1000000), 3) as max_value,
    COUNT(*) as sql_count
FROM v$sql
WHERE executions > 0
AND UPPER(sql_text) LIKE '%CUSTOMER_INFO%'
UNION ALL
SELECT 
    'Cumulative SQL executions / 3600 (not TPS)',
    ROUND(SUM(executions) / 3600, 2), -- 累计SQL执行次数除以3600,不代表一小时吞吐量或业务TPS
    ROUND(MAX(executions), 2),
    COUNT(*)
FROM v$sql
WHERE executions > 0
AND UPPER(sql_text) LIKE '%TRANSACTION_LOG%'
UNION ALL
SELECT 
    'Resource Usage',
    ROUND(AVG(buffer_gets / executions), 2),
    ROUND(MAX(buffer_gets / executions), 2),
    COUNT(*)
FROM v$sql
WHERE executions > 0
AND buffer_gets > 0;

Oracle SQL调优是一个系统性工程,需要结合业务特点、数据特征和系统资源进行综合考虑。在银行环境中,稳定性和准确性同样重要,调优时需要充分测试确保不影响业务正确性。