Oracle高阶篇 - 我的Oracle学习笔记:实践、优化与深思

Oracle数据库进阶实战篇

一、项目架构设计

1.1 完整的学生成绩管理系统架构

-- 在students表基础上构建完整系统
-- 1. 创建院系维度表
CREATE TABLE departments AS
SELECT 
    ROWNUM as dept_id,
    CASE 
        WHEN 学科编号 = 101 THEN '文学院'
        WHEN 学科编号 = 102 THEN '理学院' 
        WHEN 学科编号 = 103 THEN '外语学院'
        ELSE '通用学院'
    END as dept_name,
    CASE 
        WHEN 学科编号 = 101 THEN '文学类学科'
        WHEN 学科编号 = 102 THEN '数理类学科'
        WHEN 学科编号 = 103 THEN '语言类学科'
        ELSE '综合类'
    END as dept_type
FROM (SELECT DISTINCT 学科编号 FROM students WHERE ROWNUM <= 3);

-- 2. 创建时间维度表(模拟考试时间)
CREATE TABLE exam_periods AS
SELECT 
    ROWNUM as period_id,
    ADD_MONTHS(DATE '2023-09-01', (ROWNUM-1)*3) as exam_date,
    CASE ROWNUM
        WHEN 1 THEN '第一学期期中'
        WHEN 2 THEN '第一学期期末'
        WHEN 3 THEN '第二学期期中'
        WHEN 4 THEN '第二学期期末'
    END as period_name
FROM dual CONNECT BY ROWNUM <= 4;

-- 3. 扩展主表(不修改原结构,通过视图扩展)
CREATE OR REPLACE VIEW v_students_comprehensive AS
SELECT 
    s.学号,
    s.姓名,
    s.性别,
    s.学科编号,
    s.学科,
    s.成绩,
    d.dept_name as 院系,
    d.dept_type as 学科类型,
    ep.period_name as 考试期次,
    ep.exam_date as 考试日期,
    -- 动态计算字段
    CASE 
        WHEN s.成绩 >= 130 THEN '卓越'
        WHEN s.成绩 >= 115 THEN '优秀'
        WHEN s.成绩 >= 90 THEN '良好'
        WHEN s.成绩 >= 60 THEN '及格'
        ELSE '不及格'
    END as 成绩等级,
    -- 标准化分数
    ROUND((s.成绩 - AVG(s.成绩) OVER (PARTITION BY s.学科)) / 
          STDDEV(s.成绩) OVER (PARTITION BY s.学科), 2) as 标准分
FROM students s
CROSS JOIN (SELECT * FROM exam_periods WHERE period_id = 1) ep
LEFT JOIN departments d ON s.学科编号 = d.dept_id;

二、高级分析函数实战

2.1 多层次分析查询

-- 学生成绩全景分析
WITH student_analysis AS (
    SELECT 
        学号,
        姓名,
        性别,
        学科,
        成绩,
        -- 个人层面分析
        AVG(成绩) OVER (PARTITION BY 姓名) as 个人平均分,
        COUNT(*) OVER (PARTITION BY 姓名) as 个人科目数,

        -- 学科层面分析  
        AVG(成绩) OVER (PARTITION BY 学科) as 学科平均分,
        MAX(成绩) OVER (PARTITION BY 学科) as 学科最高分,
        MIN(成绩) OVER (PARTITION BY 学科) as 学科最低分,

        -- 排名分析
        ROW_NUMBER() OVER (PARTITION BY 学科 ORDER BY 成绩 DESC) as 学科内排名,
        DENSE_RANK() OVER (ORDER BY AVG(成绩) OVER (PARTITION BY 姓名) DESC) as 总排名,

        -- 趋势分析
        LAG(成绩) OVER (PARTITION BY 姓名 ORDER BY 学科) as 前一科成绩,
        LEAD(成绩) OVER (PARTITION BY 姓名 ORDER BY 学科) as 后一科成绩,

        -- 分布分析
        PERCENT_RANK() OVER (PARTITION BY 学科 ORDER BY 成绩) as 成绩百分位,
        NTILE(4) OVER (PARTITION BY 学科 ORDER BY 成绩) as 成绩四分位,

        -- 稳定性分析
        STDDEV(成绩) OVER (PARTITION BY 姓名) as 成绩标准差,
        MAX(成绩) OVER (PARTITION BY 姓名) - MIN(成绩) OVER (PARTITION BY 姓名) as 成绩极差

    FROM students
)
SELECT 
    姓名,
    学科,
    成绩,
    个人平均分,
    学科平均分,
    学科内排名,
    总排名,
    ROUND(成绩标准差, 2) as 稳定性指标,
    CASE 
        WHEN 成绩 > 学科平均分 THEN '高于平均'
        ELSE '低于平均'
    END as 学科比较,
    CASE 
        WHEN 成绩标准差 < 10 THEN '成绩稳定'
        WHEN 成绩标准差 BETWEEN 10 AND 20 THEN '成绩波动一般'
        ELSE '成绩波动较大'
    END as 稳定性评价
FROM student_analysis
ORDER BY 总排名, 姓名, 学科;

2.2 移动窗口与累积分析

-- 复杂窗口函数应用
SELECT 
    姓名,
    学科,
    成绩,
    -- 移动平均(3科移动窗口)
    ROUND(AVG(成绩) OVER (
        PARTITION BY 姓名 
        ORDER BY 学科
        ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
    ), 2) as 三科移动平均,

    -- 累积统计
    SUM(成绩) OVER (
        PARTITION BY 姓名 
        ORDER BY 学科
        ROWS UNBOUNDED PRECEDING
    ) as 累计总分,

    AVG(成绩) OVER (
        PARTITION BY 姓名 
        ORDER BY 学科
        ROWS UNBOUNDED PRECEDING
    ) as 累计平均分,

    -- 同比环比分析(模拟多期数据)
    ROUND((成绩 - LAG(成绩, 1) OVER (PARTITION BY 姓名, 学科 ORDER BY 学号)) / 
          LAG(成绩, 1) OVER (PARTITION BY 姓名, 学科 ORDER BY 学号) * 100, 2) as 成绩增长率,

    -- 排名变化趋势
    RANK() OVER (PARTITION BY 学科 ORDER BY 成绩 DESC) -
    LAG(RANK() OVER (PARTITION BY 学科 ORDER BY 成绩 DESC)) 
    OVER (PARTITION BY 姓名 ORDER BY 学科) as 排名变化,

    -- 累积分布
    CUME_DIST() OVER (PARTITION BY 学科 ORDER BY 成绩) as 累积分布比例

FROM students
ORDER BY 姓名, 学科;

三、性能优化实战

3.1 执行计划深度优化

-- 复杂查询执行计划分析
EXPLAIN PLAN FOR
WITH subject_stats AS (
    SELECT 
        学科,
        AVG(成绩) as avg_score,
        STDDEV(成绩) as std_score,
        COUNT(*) as student_count
    FROM students
    GROUP BY 学科
),
student_performance AS (
    SELECT 
        姓名,
        AVG(成绩) as personal_avg,
        COUNT(*) as subject_count,
        MAX(成绩) as best_score,
        MIN(成绩) as worst_score
    FROM students
    GROUP BY 姓名
    HAVING COUNT(*) = 3
),
ranked_students AS (
    SELECT 
        s.学号,
        s.姓名,
        s.学科,
        s.成绩,
        sp.personal_avg,
        ss.avg_score as subject_avg,
        (s.成绩 - ss.avg_score) / ss.std_score as z_score,
        CASE 
            WHEN s.成绩 > ss.avg_score + ss.std_score THEN '超常发挥'
            WHEN s.成绩 > ss.avg_score THEN '正常发挥'
            ELSE '发挥失常'
        END as performance_level
    FROM students s
    JOIN student_performance sp ON s.姓名 = sp.姓名
    JOIN subject_stats ss ON s.学科 = ss.学科
    WHERE s.成绩 > ss.avg_score
)
SELECT 
    rs.姓名,
    rs.学科,
    rs.成绩,
    rs.personal_avg,
    rs.subject_avg,
    ROUND(rs.z_score, 2) as 标准分,
    rs.performance_level as 发挥水平,
    RANK() OVER (ORDER BY rs.z_score DESC) as 综合排名
FROM ranked_students rs
ORDER BY 综合排名, rs.姓名;

-- 显示执行计划
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

-- 创建优化索引
CREATE INDEX idx_students_comprehensive ON students(姓名, 学科, 成绩);
CREATE INDEX idx_students_subject_score ON students(学科, 成绩);
CREATE INDEX idx_students_name_perf ON students(姓名, 成绩);

-- 使用提示强制优化
SELECT /*+ INDEX(s IDX_STUDENTS_COMPREHENSIVE) USE_NL(sp) */
    s.姓名,
    s.学科,
    s.成绩
FROM students s
WHERE EXISTS (
    SELECT 1 FROM students s2 
    WHERE s2.姓名 = s.姓名 
    GROUP BY s2.姓名 
    HAVING AVG(s2.成绩) > 100
);

3.2 物化视图实战

-- 创建学生成绩汇总物化视图
CREATE MATERIALIZED VIEW mv_student_analytics
BUILD IMMEDIATE
REFRESH COMPLETE ON DEMAND
ENABLE QUERY REWRITE
AS
SELECT 
    姓名,
    性别,
    学科,
    COUNT(*) as 考试次数,
    AVG(成绩) as 平均成绩,
    MAX(成绩) as 最高成绩,
    MIN(成绩) as 最低成绩,
    STDDEV(成绩) as 成绩标准差,
    ROUND(AVG(成绩) / MAX(AVG(成绩)) OVER (PARTITION BY 学科) * 100, 2) as 相对百分比,
    RANK() OVER (PARTITION BY 学科 ORDER BY AVG(成绩) DESC) as 学科内平均分排名
FROM students
GROUP BY 姓名, 性别, 学科;

-- 创建物化视图日志
CREATE MATERIALIZED VIEW LOG ON students 
WITH ROWID, SEQUENCE(学号, 姓名, 性别, 学科, 成绩)
INCLUDING NEW VALUES;

-- 自动查询重写示例
SELECT /*+ REWRITE_OR_ERROR */
    姓名,
    学科,
    平均成绩,
    成绩标准差
FROM mv_student_analytics
WHERE 学科 = '数学' AND 平均成绩 > 100;

-- 物化视图维护
BEGIN
    DBMS_MVIEW.REFRESH('MV_STUDENT_ANALYTICS', 'C');
END;
/

-- 监控物化视图使用
SELECT 
    mview_name,
    staleness,
    last_refresh_date,
    query
FROM user_mviews 
WHERE mview_name = 'MV_STUDENT_ANALYTICS';

四、PL/SQL高级编程

4.1 面向对象编程实践

-- 创建对象类型
CREATE OR REPLACE TYPE student_obj AS OBJECT (
    student_id NUMBER,
    student_name VARCHAR2(20),
    gender VARCHAR2(20),
    subject_scores subject_score_list,

    MEMBER FUNCTION get_average_score RETURN NUMBER,
    MEMBER FUNCTION get_best_subject RETURN VARCHAR2,
    MEMBER PROCEDURE display_info
);
/

CREATE OR REPLACE TYPE BODY student_obj AS
    MEMBER FUNCTION get_average_score RETURN NUMBER IS
        total NUMBER := 0;
        count_subjects NUMBER := 0;
    BEGIN
        FOR i IN 1..subject_scores.COUNT LOOP
            total := total + subject_scores(i).score;
            count_subjects := count_subjects + 1;
        END LOOP;
        RETURN ROUND(total / count_subjects, 2);
    END;

    MEMBER FUNCTION get_best_subject RETURN VARCHAR2 IS
        best_score NUMBER := 0;
        best_subject VARCHAR2(20);
    BEGIN
        FOR i IN 1..subject_scores.COUNT LOOP
            IF subject_scores(i).score > best_score THEN
                best_score := subject_scores(i).score;
                best_subject := subject_scores(i).subject;
            END IF;
        END LOOP;
        RETURN best_subject;
    END;

    MEMBER PROCEDURE display_info IS
    BEGIN
        DBMS_OUTPUT.PUT_LINE('学生: ' || student_name);
        DBMS_OUTPUT.PUT_LINE('平均分: ' || get_average_score);
        DBMS_OUTPUT.PUT_LINE('优势学科: ' || get_best_subject);
    END;
END;
/

-- 创建集合类型
CREATE OR REPLACE TYPE subject_score_obj AS OBJECT (
    subject VARCHAR2(20),
    score NUMBER
);
/

CREATE OR REPLACE TYPE subject_score_list AS TABLE OF subject_score_obj;
/

-- 使用对象类型
DECLARE
    v_student student_obj;
    v_scores subject_score_list := subject_score_list();
BEGIN
    -- 构建学生成绩数据
    SELECT subject_score_obj(学科, 成绩) 
    BULK COLLECT INTO v_scores
    FROM students 
    WHERE 姓名 = '王小美';

    -- 创建学生对象
    v_student := student_obj(1, '王小美', '女', v_scores);

    -- 调用对象方法
    v_student.display_info;
    DBMS_OUTPUT.PUT_LINE('平均分: ' || v_student.get_average_score);
    DBMS_OUTPUT.PUT_LINE('最佳学科: ' || v_student.get_best_subject);
END;
/

4.2 高级错误处理与日志系统

-- 创建系统日志表
CREATE TABLE system_logs (
    log_id NUMBER PRIMARY KEY,
    log_time TIMESTAMP DEFAULT SYSTIMESTAMP,
    log_level VARCHAR2(20),
    module_name VARCHAR2(50),
    message VARCHAR2(1000),
    error_code NUMBER,
    error_stack CLOB
);

CREATE SEQUENCE log_seq START WITH 1 INCREMENT BY 1;

-- 创建高级日志包
CREATE OR REPLACE PACKAGE advanced_logging_pkg AS
    PROCEDURE log_info(p_module VARCHAR2, p_message VARCHAR2);
    PROCEDURE log_error(p_module VARCHAR2, p_message VARCHAR2, p_error_code NUMBER DEFAULT NULL);
    PROCEDURE log_debug(p_module VARCHAR2, p_message VARCHAR2);
    FUNCTION get_recent_logs(p_minutes NUMBER DEFAULT 60) RETURN SYS_REFCURSOR;
END advanced_logging_pkg;
/

CREATE OR REPLACE PACKAGE BODY advanced_logging_pkg AS

    PROCEDURE log_info(p_module VARCHAR2, p_message VARCHAR2) IS
    BEGIN
        INSERT INTO system_logs (log_id, log_level, module_name, message)
        VALUES (log_seq.NEXTVAL, 'INFO', p_module, p_message);
        COMMIT;
    END;

    PROCEDURE log_error(p_module VARCHAR2, p_message VARCHAR2, p_error_code NUMBER DEFAULT NULL) IS
    BEGIN
        INSERT INTO system_logs (log_id, log_level, module_name, message, error_code, error_stack)
        VALUES (log_seq.NEXTVAL, 'ERROR', p_module, p_message, p_error_code, DBMS_UTILITY.FORMAT_ERROR_STACK);
        COMMIT;
    END;

    PROCEDURE log_debug(p_module VARCHAR2, p_message VARCHAR2) IS
    BEGIN
        INSERT INTO system_logs (log_id, log_level, module_name, message)
        VALUES (log_seq.NEXTVAL, 'DEBUG', p_module, p_message);
        COMMIT;
    END;

    FUNCTION get_recent_logs(p_minutes NUMBER DEFAULT 60) RETURN SYS_REFCURSOR IS
        v_cursor SYS_REFCURSOR;
    BEGIN
        OPEN v_cursor FOR
        SELECT log_time, log_level, module_name, message, error_code
        FROM system_logs
        WHERE log_time >= SYSTIMESTAMP - (p_minutes/1440)
        ORDER BY log_time DESC;

        RETURN v_cursor;
    END;

END advanced_logging_pkg;
/

-- 使用高级错误处理的存储过程
CREATE OR REPLACE PROCEDURE process_student_scores IS
    CURSOR student_cursor IS
        SELECT DISTINCT 姓名 FROM students;

    v_total_processed NUMBER := 0;
    v_errors_occurred NUMBER := 0;
BEGIN
    advanced_logging_pkg.log_info('PROCESS_STUDENT_SCORES', '开始处理学生成绩数据');

    FOR student_rec IN student_cursor LOOP
        BEGIN
            -- 模拟复杂处理逻辑
            advanced_logging_pkg.log_debug('PROCESS_STUDENT', '处理学生: ' || student_rec.姓名);

            -- 这里可以添加实际的业务逻辑
            DBMS_LOCK.SLEEP(0.01); -- 模拟处理时间

            v_total_processed := v_total_processed + 1;

            -- 模拟随机错误(用于测试)
            IF DBMS_RANDOM.VALUE < 0.1 THEN
                RAISE_APPLICATION_ERROR(-20001, '模拟处理错误');
            END IF;

        EXCEPTION
            WHEN OTHERS THEN
                v_errors_occurred := v_errors_occurred + 1;
                advanced_logging_pkg.log_error('PROCESS_STUDENT', 
                    '处理学生 ' || student_rec.姓名 || ' 时发生错误', SQLCODE);
        END;
    END LOOP;

    advanced_logging_pkg.log_info('PROCESS_STUDENT_SCORES', 
        '处理完成: 成功 ' || v_total_processed || ', 错误 ' || v_errors_occurred);

EXCEPTION
    WHEN OTHERS THEN
        advanced_logging_pkg.log_error('PROCESS_STUDENT_SCORES', 
            '处理过程中发生严重错误', SQLCODE);
        RAISE;
END;
/

-- 测试错误处理
BEGIN
    process_student_scores;

    -- 查看日志
    DECLARE
        v_cursor SYS_REFCURSOR;
        v_log_time TIMESTAMP;
        v_log_level VARCHAR2(20);
        v_module VARCHAR2(50);
        v_message VARCHAR2(1000);
    BEGIN
        v_cursor := advanced_logging_pkg.get_recent_logs(10);
        LOOP
            FETCH v_cursor INTO v_log_time, v_log_level, v_module, v_message;
            EXIT WHEN v_cursor%NOTFOUND;
            DBMS_OUTPUT.PUT_LINE(v_log_level || ' | ' || v_module || ' | ' || v_message);
        END LOOP;
        CLOSE v_cursor;
    END;
END;
/

五、高级事务与并发控制

5.1 分布式事务模拟

-- 创建模拟的分布式表
CREATE TABLE student_scores_local AS SELECT * FROM students;
CREATE TABLE student_scores_remote AS SELECT * FROM students WHERE 1=0;

-- 分布式事务管理
DECLARE
    v_transaction_id VARCHAR2(100);
    v_commit_count NUMBER := 0;
BEGIN
    -- 开始分布式事务
    v_transaction_id := DBMS_TRANSACTION.LOCAL_TRANSACTION_ID;
    DBMS_OUTPUT.PUT_LINE('事务ID: ' || v_transaction_id);

    -- 模拟分布式操作
    SAVEPOINT before_distributed_ops;

    -- 操作1:更新本地表
    UPDATE student_scores_local 
    SET 成绩 = 成绩 + 5 
    WHERE 姓名 = '王小美';

    -- 操作2:插入到远程表(模拟)
    INSERT INTO student_scores_remote 
    SELECT * FROM student_scores_local 
    WHERE 姓名 = '王小美';

    -- 两阶段提交准备
    v_commit_count := SQL%ROWCOUNT;

    -- 检查业务规则
    IF v_commit_count > 0 THEN
        -- 第一阶段:预提交
        DBMS_OUTPUT.PUT_LINE('预提交检查通过');

        -- 第二阶段:正式提交
        COMMIT;
        DBMS_OUTPUT.PUT_LINE('分布式事务提交成功');
    ELSE
        ROLLBACK TO before_distributed_ops;
        DBMS_OUTPUT.PUT_LINE('事务回滚:无数据变更');
    END IF;

EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        DBMS_OUTPUT.PUT_LINE('分布式事务失败: ' || SQLERRM);
END;
/

-- 高级锁管理
DECLARE
    v_lock_handle VARCHAR2(128);
    v_lock_result NUMBER;
BEGIN
    -- 获取用户定义锁
    v_lock_result := DBMS_LOCK.ALLOCATE_UNIQUE('STUDENT_SCORE_PROCESSING', v_lock_handle);

    -- 请求排他锁(等待10秒)
    v_lock_result := DBMS_LOCK.REQUEST(
        lockhandle => v_lock_handle,
        lockmode => DBMS_LOCK.X_MODE,
        timeout => 10,
        release_on_commit => TRUE
    );

    IF v_lock_result = 0 THEN
        DBMS_OUTPUT.PUT_LINE('成功获取锁,开始处理数据...');

        -- 执行需要锁保护的操作
        UPDATE students SET 成绩 = 成绩 + 1 WHERE 学号 = 1;
        COMMIT;

        DBMS_OUTPUT.PUT_LINE('数据处理完成,锁已释放');
    ELSE
        DBMS_OUTPUT.PUT_LINE('获取锁失败,错误代码: ' || v_lock_result);
    END IF;
END;
/

5.2 高级并发模式

-- 乐观并发控制实现
CREATE TABLE students_optimistic (
    学号 NUMBER,
    姓名 VARCHAR2(20),
    性别 VARCHAR2(20),
    学科编号 NUMBER,
    学科 VARCHAR2(20),
    成绩 NUMBER,
    version_number NUMBER DEFAULT 1,
    last_updated TIMESTAMP DEFAULT SYSTIMESTAMP,
    last_updated_by VARCHAR2(50) DEFAULT USER
);

-- 插入测试数据
INSERT INTO students_optimistic 
SELECT 学号, 姓名, 性别, 学科编号, 学科, 成绩, 1, SYSTIMESTAMP, USER 
FROM students;

COMMIT;

-- 乐观锁更新函数
CREATE OR REPLACE FUNCTION update_student_optimistic(
    p_student_id IN NUMBER,
    p_subject IN VARCHAR2,
    p_new_score IN NUMBER,
    p_expected_version IN NUMBER
) RETURN VARCHAR2 AS
    v_current_version NUMBER;
    v_current_score NUMBER;
    v_rows_updated NUMBER;
BEGIN
    -- 检查版本号
    SELECT version_number, 成绩 
    INTO v_current_version, v_current_score
    FROM students_optimistic
    WHERE 学号 = p_student_id AND 学科 = p_subject
    FOR UPDATE;  -- 行级锁

    IF v_current_version != p_expected_version THEN
        RETURN 'ERROR:数据已被其他用户修改,当前版本:' || v_current_version;
    END IF;

    -- 更新数据
    UPDATE students_optimistic 
    SET 成绩 = p_new_score,
        version_number = version_number + 1,
        last_updated = SYSTIMESTAMP,
        last_updated_by = USER
    WHERE 学号 = p_student_id 
    AND 学科 = p_subject
    AND version_number = p_expected_version;

    v_rows_updated := SQL%ROWCOUNT;

    IF v_rows_updated = 1 THEN
        COMMIT;
        RETURN 'SUCCESS:成绩从' || v_current_score || '更新为' || p_new_score;
    ELSE
        ROLLBACK;
        RETURN 'ERROR:更新失败,可能版本号已变更';
    END IF;

EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RETURN 'ERROR:记录不存在';
    WHEN OTHERS THEN
        ROLLBACK;
        RETURN 'ERROR:' || SQLERRM;
END;
/

-- 测试乐观锁
DECLARE
    v_result VARCHAR2(200);
BEGIN
    -- 第一次更新(应该成功)
    v_result := update_student_optimistic(1, '语文', 118, 1);
    DBMS_OUTPUT.PUT_LINE('第一次更新: ' || v_result);

    -- 第二次更新(应该失败,因为版本号不匹配)
    v_result := update_student_optimistic(1, '语文', 120, 1);
    DBMS_OUTPUT.PUT_LINE('第二次更新: ' || v_result);
END;
/

-- 批量并发处理
CREATE OR REPLACE PROCEDURE process_students_concurrent IS
    PRAGMA AUTONOMOUS_TRANSACTION;
    CURSOR student_cursor IS
        SELECT 学号, 姓名, 学科, 成绩, version_number
        FROM students_optimistic
        WHERE 成绩 < 100
        FOR UPDATE SKIP LOCKED;  -- 跳过已被锁定的行

    v_processed NUMBER := 0;
    v_updated NUMBER := 0;
BEGIN
    FOR rec IN student_cursor LOOP
        BEGIN
            -- 模拟复杂处理
            DBMS_LOCK.SLEEP(0.1);

            -- 尝试更新
            UPDATE students_optimistic 
            SET 成绩 = 成绩 + 10,
                version_number = version_number + 1
            WHERE 学号 = rec.学号 
            AND 学科 = rec.学科
            AND version_number = rec.version_number;

            v_processed := v_processed + 1;
            IF SQL%ROWCOUNT = 1 THEN
                v_updated := v_updated + 1;
            END IF;

            -- 每处理10条提交一次,减少锁持有时间
            IF MOD(v_processed, 10) = 0 THEN
                COMMIT;
            END IF;

        EXCEPTION
            WHEN OTHERS THEN
                DBMS_OUTPUT.PUT_LINE('处理学生 ' || rec.姓名 || ' 时出错: ' || SQLERRM);
        END;
    END LOOP;

    COMMIT;
    DBMS_OUTPUT.PUT_LINE('并发处理完成: 处理' || v_processed || '条, 成功更新' || v_updated || '条');
END;
/

-- 在多个会话中并发执行测试
-- 会话1: BEGIN process_students_concurrent; END;
-- 会话2: BEGIN process_students_concurrent; END;

六、系统集成与自动化

6.1 数据导出与外部表

-- 创建目录对象
CREATE OR REPLACE DIRECTORY student_data_dir AS '/tmp/oracle_data';
GRANT READ, WRITE ON DIRECTORY student_data_dir TO PUBLIC;

-- 使用UTL_FILE包导出数据
DECLARE
    v_file UTL_FILE.FILE_TYPE;
    CURSOR student_cursor IS
        SELECT 姓名, 学科, 成绩, 
               ROUND(AVG(成绩) OVER (PARTITION BY 姓名), 2) as 平均分
        FROM students
        ORDER BY 姓名, 学科;
BEGIN
    -- 打开文件
    v_file := UTL_FILE.FOPEN('STUDENT_DATA_DIR', 'student_scores_export.csv', 'W', 32767);

    -- 写入表头
    UTL_FILE.PUT_LINE(v_file, '姓名,学科,成绩,个人平均分,导出时间');

    -- 写入数据
    FOR rec IN student_cursor LOOP
        UTL_FILE.PUT_LINE(v_file, 
            rec.姓名 || ',' || 
            rec.学科 || ',' || 
            rec.成绩 || ',' || 
            rec.平均分 || ',' ||
            TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS')
        );
    END LOOP;

    -- 关闭文件
    UTL_FILE.FCLOSE(v_file);

    DBMS_OUTPUT.PUT_LINE('数据导出完成: student_scores_export.csv');
EXCEPTION
    WHEN OTHERS THEN
        IF UTL_FILE.IS_OPEN(v_file) THEN
            UTL_FILE.FCLOSE(v_file);
        END IF;
        RAISE;
END;
/

-- 创建外部表读取CSV文件
CREATE TABLE students_external (
    姓名 VARCHAR2(20),
    学科 VARCHAR2(20),
    成绩 NUMBER,
    个人平均分 NUMBER,
    导出时间 VARCHAR2(20)
)
ORGANIZATION EXTERNAL (
    TYPE ORACLE_LOADER
    DEFAULT DIRECTORY student_data_dir
    ACCESS PARAMETERS (
        RECORDS DELIMITED BY NEWLINE
        SKIP 1  -- 跳过表头
        FIELDS TERMINATED BY ','
        MISSING FIELD VALUES ARE NULL
        (
            姓名 CHAR(20),
            学科 CHAR(20),
            成绩 CHAR(10),
            个人平均分 CHAR(10),
            导出时间 CHAR(20)
        )
    )
    LOCATION ('student_scores_export.csv')
)
REJECT LIMIT UNLIMITED;

-- 查询外部表
SELECT * FROM students_external;

6.2 自动化作业调度

-- 创建自动化作业
BEGIN
    DBMS_SCHEDULER.CREATE_JOB (
        job_name        => 'DAILY_STUDENT_ANALYSIS',
        job_type        => 'PLSQL_BLOCK',
        job_action      => 'BEGIN 
                            advanced_logging_pkg.log_info(''DAILY_JOB'', ''开始每日学生分析'');
                            -- 这里可以调用各种分析过程
                            process_student_scores;
                            advanced_logging_pkg.log_info(''DAILY_JOB'', ''每日学生分析完成'');
                           END;',
        start_date      => SYSTIMESTAMP,
        repeat_interval => 'FREQ=DAILY; BYHOUR=2; BYMINUTE=0',  -- 每天凌晨2点
        enabled         => TRUE,
        comments        => '每日学生成绩分析作业'
    );
END;
/

-- 创建数据清理作业
BEGIN
    DBMS_SCHEDULER.CREATE_JOB (
        job_name        => 'WEEKLY_DATA_MAINTENANCE',
        job_type        => 'PLSQL_BLOCK',
        job_action      => 'BEGIN 
                            -- 清理30天前的日志
                            DELETE FROM system_logs 
                            WHERE log_time < SYSTIMESTAMP - 30;
                            COMMIT;

                            -- 刷新物化视图
                            DBMS_MVIEW.REFRESH(''MV_STUDENT_ANALYTICS'', ''C'');

                            advanced_logging_pkg.log_info(''MAINTENANCE_JOB'', ''每周维护完成'');
                           END;',
        start_date      => SYSTIMESTAMP,
        repeat_interval => 'FREQ=WEEKLY; BYDAY=SUN; BYHOUR=3',  -- 每周日凌晨3点
        enabled         => TRUE,
        comments        => '每周数据维护作业'
    );
END;
/

-- 监控作业运行状态
SELECT 
    job_name,
    state,
    last_start_date,
    next_run_date,
    run_count,
    failure_count
FROM USER_SCHEDULER_JOBS
WHERE job_name LIKE '%STUDENT%' OR job_name LIKE '%MAINTENANCE%';

-- 创建监控视图
CREATE OR REPLACE VIEW v_system_monitoring AS
SELECT 
    '学生记录数' as metric_name,
    TO_CHAR(COUNT(*)) as metric_value
FROM students
UNION ALL
SELECT 
    '平均成绩',
    TO_CHAR(ROUND(AVG(成绩), 2))
FROM students
UNION ALL
SELECT 
    '系统日志数',
    TO_CHAR(COUNT(*))
FROM system_logs
WHERE log_time >= SYSDATE - 1
UNION ALL
SELECT 
    '物化视图状态',
    staleness
FROM user_mviews 
WHERE mview_name = 'MV_STUDENT_ANALYTICS';

-- 查询系统监控
SELECT * FROM v_system_monitoring;

项目总结

本实战项目基于简单的students表,逐步构建了一个完整的学生成绩管理系统,涵盖了:

  1. 系统架构 – 维度建模和视图扩展
  2. 高级分析 – 多层次窗口函数和复杂统计
  3. 性能优化 – 执行计划分析和物化视图
  4. PL/SQL编程 – 面向对象和高级错误处理
  5. 并发控制 – 乐观锁和分布式事务
  6. 系统集成 – 数据导出和作业调度

这个项目展示了如何在有限的表结构基础上,通过Oracle数据库的高级特性构建出企业级的应用系统。


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注