【Oracle】DML语言

在这里插入图片描述

个人主页:Guiat
归属专栏:Oracle

在这里插入图片描述

正文
DML(Data Manipulation Language)是Oracle数据库的"魔法师",专门负责数据的增删改查操作。如果说DDL是建房子的,那DML就是在房子里生活的——添置家具、整理物品、搬家换房。它让静态的数据结构变得生动起来,是我们与数据库数据打交道最频繁的语言!

1. DML概述

1.1 什么是DML?

DML就像是数据库的"生活管家",负责管理数据库中的实际数据。它不改变数据库的结构,而是专注于数据内容的操作。在Oracle这个数据王国里,DML是让数据"活"起来的关键工具。

DML数据操作语言
INSERT插入
UPDATE更新
DELETE删除
MERGE合并
SELECT查询
单行插入
批量插入
条件更新
批量更新
条件删除
批量删除
插入或更新
数据同步
简单查询
复杂查询

1.2 DML的核心功能

Oracle DML的功能体系就像一个完整的数据处理工厂:

Oracle DML功能体系
数据插入
数据更新
数据删除
数据合并
事务控制
性能优化
VALUES插入
子查询插入
多表插入
批量插入
简单更新
关联更新
条件更新
批量更新
条件删除
关联删除
批量删除
级联删除
MERGE语句
UPSERT操作
数据同步
COMMIT提交
ROLLBACK回滚
SAVEPOINT保存点
批量操作
并行处理
索引优化

2. INSERT语句详解

2.1 基础插入操作

INSERT就像往盒子里放东西,有很多种放法:

-- 最基本的插入
INSERT INTO employees (employee_id, first_name, last_name, email, hire_date)
VALUES (1001, 'John', 'Doe', 'john.doe@company.com', SYSDATE);

-- 插入所有列(按表结构顺序)
INSERT INTO employees
VALUES (1002, 'Jane', 'Smith', 'jane.smith@company.com', '555-1234', 
        SYSDATE, 'IT_PROG', 5000, NULL, 100, 20);

-- 插入部分列(其他列使用默认值或NULL)
INSERT INTO employees (employee_id, first_name, last_name, email)
VALUES (1003, 'Bob', 'Johnson', 'bob.johnson@company.com');

-- 使用序列插入
INSERT INTO employees (employee_id, first_name, last_name, email, hire_date)
VALUES (emp_seq.NEXTVAL, 'Alice', 'Brown', 'alice.brown@company.com', SYSDATE);

-- 插入计算值
INSERT INTO order_summary (order_id, order_date, total_amount, tax_amount, final_amount)
VALUES (1001, SYSDATE, 1000, 1000 * 0.08, 1000 * 1.08);

-- 插入函数结果
INSERT INTO audit_log (log_id, table_name, action_type, action_date, username)
VALUES (audit_seq.NEXTVAL, 'EMPLOYEES', 'INSERT', SYSTIMESTAMP, USER);

2.2 子查询插入

用子查询插入就像批量搬家,一次性处理大量数据:

-- 从其他表插入数据
INSERT INTO employees_backup
SELECT * FROM employees WHERE department_id = 20;

-- 插入聚合数据
INSERT INTO department_summary (dept_id, dept_name, emp_count, avg_salary, total_salary)
SELECT 
    d.department_id,
    d.department_name,
    COUNT(e.employee_id),
    AVG(e.salary),
    SUM(e.salary)
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_id, d.department_name;

-- 插入复杂计算结果
INSERT INTO sales_analysis (region, year, month, total_sales, growth_rate)
SELECT 
    region,
    EXTRACT(YEAR FROM sale_date) as year,
    EXTRACT(MONTH FROM sale_date) as month,
    SUM(amount) as total_sales,
    ROUND(
        (SUM(amount) - LAG(SUM(amount)) OVER (
            PARTITION BY region 
            ORDER BY EXTRACT(YEAR FROM sale_date), EXTRACT(MONTH FROM sale_date)
        )) / LAG(SUM(amount)) OVER (
            PARTITION BY region 
            ORDER BY EXTRACT(YEAR FROM sale_date), EXTRACT(MONTH FROM sale_date)
        ) * 100, 2
    ) as growth_rate
FROM sales_data
GROUP BY region, EXTRACT(YEAR FROM sale_date), EXTRACT(MONTH FROM sale_date);

-- 条件插入
INSERT INTO high_performers (employee_id, performance_score, bonus_eligible)
SELECT 
    employee_id,
    (salary * 0.1 + COALESCE(commission_pct * salary, 0)) as performance_score,
    CASE WHEN salary > 8000 THEN 'Y' ELSE 'N' END
FROM employees
WHERE hire_date < ADD_MONTHS(SYSDATE, -12)
  AND department_id IN (10, 20, 30);

2.3 多表插入

Oracle的多表插入功能就像是"一箭多雕",一次操作影响多个表:

-- 无条件多表插入
INSERT ALL
    INTO sales_summary (region, total_amount) VALUES (region, amount)
    INTO sales_detail (sale_id, customer_id, amount) VALUES (sale_id, customer_id, amount)
    INTO sales_audit (sale_id, insert_date) VALUES (sale_id, SYSDATE)
SELECT sale_id, region, customer_id, amount
FROM sales_data
WHERE sale_date = TRUNC(SYSDATE);

-- 条件多表插入
INSERT ALL
    WHEN amount > 10000 THEN
        INTO high_value_sales (sale_id, amount, customer_id) 
        VALUES (sale_id, amount, customer_id)
    WHEN amount BETWEEN 1000 AND 10000 THEN
        INTO medium_value_sales (sale_id, amount, customer_id) 
        VALUES (sale_id, amount, customer_id)
    ELSE
        INTO low_value_sales (sale_id, amount, customer_id) 
        VALUES (sale_id, amount, customer_id)
SELECT sale_id, amount, customer_id
FROM sales_data
WHERE sale_date >= TRUNC(SYSDATE);

-- 旋转插入(数据透视)
INSERT ALL
    INTO quarterly_sales (year, quarter, region, amount) 
    VALUES (year, 1, region, q1_sales)
    INTO quarterly_sales (year, quarter, region, amount) 
    VALUES (year, 2, region, q2_sales)
    INTO quarterly_sales (year, quarter, region, amount) 
    VALUES (year, 3, region, q3_sales)
    INTO quarterly_sales (year, quarter, region, amount) 
    VALUES (year, 4, region, q4_sales)
SELECT year, region, q1_sales, q2_sales, q3_sales, q4_sales
FROM annual_sales_pivot;

-- 复杂的条件多表插入
INSERT ALL
    WHEN department_id = 10 THEN
        INTO dept_10_employees (employee_id, name, salary) 
        VALUES (employee_id, first_name || ' ' || last_name, salary)
        INTO dept_10_salaries (employee_id, salary, bonus) 
        VALUES (employee_id, salary, salary * 0.1)
    WHEN department_id = 20 THEN
        INTO dept_20_employees (employee_id, name, salary) 
        VALUES (employee_id, first_name || ' ' || last_name, salary)
    WHEN salary > 5000 THEN
        INTO high_earners (employee_id, salary, department_id) 
        VALUES (employee_id, salary, department_id)
SELECT employee_id, first_name, last_name, salary, department_id
FROM employees
WHERE status = 'ACTIVE';

2.4 批量插入优化

批量插入就像是用卡车运货,比一件件搬要高效得多:

-- 使用FORALL进行批量插入(PL/SQL)
DECLARE
    TYPE emp_array IS TABLE OF employees%ROWTYPE;
    l_employees emp_array;
BEGIN
    -- 准备数据
    l_employees := emp_array();
    FOR i IN 1..10000 LOOP
        l_employees.EXTEND;
        l_employees(i).employee_id := emp_seq.NEXTVAL;
        l_employees(i).first_name := 'Employee' || i;
        l_employees(i).last_name := 'Test';
        l_employees(i).email := 'emp' || i || '@test.com';
        l_employees(i).hire_date := SYSDATE;
        l_employees(i).job_id := 'IT_PROG';
        l_employees(i).salary := 5000 + MOD(i, 3000);
    END LOOP;
    
    -- 批量插入
    FORALL i IN l_employees.FIRST..l_employees.LAST
        INSERT INTO employees VALUES l_employees(i);
    
    COMMIT;
    DBMS_OUTPUT.PUT_LINE('Inserted ' || l_employees.COUNT || ' employees');
END;
/

-- 使用INSERT /*+ APPEND */ 提高性能
INSERT /*+ APPEND */ INTO employees_archive
SELECT * FROM employees WHERE hire_date < DATE '2020-01-01';

-- 并行插入
ALTER SESSION ENABLE PARALLEL DML;
INSERT /*+ PARALLEL(employees_archive, 4) */ INTO employees_archive
SELECT * FROM employees WHERE department_id IN (10, 20, 30);

-- 使用NOLOGGING减少日志
ALTER TABLE employees_temp NOLOGGING;
INSERT /*+ APPEND NOLOGGING */ INTO employees_temp
SELECT * FROM external_employee_data;
ALTER TABLE employees_temp LOGGING;

3. UPDATE语句详解

3.1 基础更新操作

UPDATE就像是重新装修房间,可以改变现有的数据:

-- 简单更新
UPDATE employees 
SET salary = 6000 
WHERE employee_id = 1001;

-- 更新多个字段
UPDATE employees 
SET salary = salary * 1.1,
    commission_pct = 0.05,
    modified_date = SYSDATE
WHERE department_id = 20;

-- 使用表达式更新
UPDATE employees 
SET salary = CASE 
    WHEN job_id = 'IT_PROG' THEN salary * 1.15
    WHEN job_id = 'SA_REP' THEN salary * 1.10
    WHEN job_id = 'ST_CLERK' THEN salary * 1.05
    ELSE salary * 1.03
END,
email = LOWER(first_name || '.' || last_name || '@company.com')
WHERE hire_date < ADD_MONTHS(SYSDATE, -12);

-- 使用函数更新
UPDATE products 
SET product_name = INITCAP(product_name),
    description = REGEXP_REPLACE(description, '\s+', ' '),
    modified_date = SYSTIMESTAMP
WHERE category_id = 5;

-- 条件更新
UPDATE orders 
SET status = 'SHIPPED',
    shipped_date = SYSDATE,
    tracking_number = 'TRK' || LPAD(order_id, 10, '0')
WHERE status = 'PROCESSING' 
  AND order_date < SYSDATE - 2;

3.2 关联更新

关联更新就像是根据其他房间的情况来调整当前房间:

-- 使用子查询更新
UPDATE employees e
SET salary = (
    SELECT AVG(salary) * 1.1
    FROM employees 
    WHERE department_id = e.department_id
)
WHERE job_id = 'IT_PROG';

-- 多列子查询更新
UPDATE employees e
SET (salary, commission_pct) = (
    SELECT j.max_salary * 0.8, 0.1
    FROM jobs j
    WHERE j.job_id = e.job_id
)
WHERE hire_date > ADD_MONTHS(SYSDATE, -6);

-- 使用EXISTS的关联更新
UPDATE products p
SET discontinued = 'Y'
WHERE NOT EXISTS (
    SELECT 1 
    FROM order_details od 
    WHERE od.product_id = p.product_id 
      AND od.order_date > ADD_MONTHS(SYSDATE, -12)
);

-- 复杂关联更新
UPDATE customers c
SET (credit_limit, customer_status) = (
    SELECT 
        CASE 
            WHEN total_orders > 100 THEN 50000
            WHEN total_orders > 50 THEN 25000
            WHEN total_orders > 10 THEN 10000
            ELSE 5000
        END,
        CASE 
            WHEN total_orders > 50 THEN 'VIP'
            WHEN total_orders > 10 THEN 'REGULAR'
            ELSE 'NEW'
        END
    FROM (
        SELECT 
            customer_id,
            COUNT(*) as total_orders,
            SUM(total_amount) as total_spent
        FROM orders
        WHERE order_date > ADD_MONTHS(SYSDATE, -12)
        GROUP BY customer_id
    ) order_stats
    WHERE order_stats.customer_id = c.customer_id
)
WHERE EXISTS (
    SELECT 1 FROM orders o 
    WHERE o.customer_id = c.customer_id
);

3.3 批量更新优化

-- 使用MERGE进行高效更新
MERGE INTO employees e
USING (
    SELECT employee_id, salary * 1.1 as new_salary
    FROM employees
    WHERE performance_rating = 'EXCELLENT'
) perf
ON (e.employee_id = perf.employee_id)
WHEN MATCHED THEN
    UPDATE SET 
        salary = perf.new_salary,
        last_raise_date = SYSDATE;

-- 分批更新大表
DECLARE
    v_batch_size NUMBER := 10000;
    v_total_updated NUMBER := 0;
    v_batch_updated NUMBER;
BEGIN
    LOOP
        UPDATE employees 
        SET status = 'INACTIVE'
        WHERE status = 'TEMP' 
          AND hire_date < ADD_MONTHS(SYSDATE, -24)
          AND ROWNUM <= v_batch_size;
        
        v_batch_updated := SQL%ROWCOUNT;
        v_total_updated := v_total_updated + v_batch_updated;
        
        COMMIT;
        
        EXIT WHEN v_batch_updated = 0;
        
        DBMS_OUTPUT.PUT_LINE('Updated ' || v_batch_updated || ' rows');
    END LOOP;
    
    DBMS_OUTPUT.PUT_LINE('Total updated: ' || v_total_updated || ' rows');
END;
/

-- 并行更新
ALTER SESSION ENABLE PARALLEL DML;
UPDATE /*+ PARALLEL(employees, 4) */ employees
SET salary = salary * 1.05
WHERE department_id IN (10, 20, 30, 40);
COMMIT;

4. DELETE语句详解

4.1 基础删除操作

DELETE就像是清理房间,移除不需要的物品:

-- 简单删除
DELETE FROM employees WHERE employee_id = 1001;

-- 条件删除
DELETE FROM employees 
WHERE status = 'TERMINATED' 
  AND termination_date < ADD_MONTHS(SYSDATE, -24);

-- 使用子查询删除
DELETE FROM order_details
WHERE order_id IN (
    SELECT order_id 
    FROM orders 
    WHERE order_date < ADD_MONTHS(SYSDATE, -36)
      AND status = 'CANCELLED'
);

-- 使用EXISTS删除
DELETE FROM products p
WHERE EXISTS (
    SELECT 1 
    FROM discontinued_products dp 
    WHERE dp.product_id = p.product_id
);

-- 复杂条件删除
DELETE FROM sales_data
WHERE sale_date < ADD_MONTHS(SYSDATE, -60)
  AND customer_id NOT IN (
      SELECT DISTINCT customer_id 
      FROM orders 
      WHERE order_date > ADD_MONTHS(SYSDATE, -12)
  )
  AND amount < 100;

4.2 关联删除

-- 删除孤儿记录
DELETE FROM order_details od
WHERE NOT EXISTS (
    SELECT 1 
    FROM orders o 
    WHERE o.order_id = od.order_id
);

-- 基于统计信息删除
DELETE FROM customers c
WHERE customer_id NOT IN (
    SELECT DISTINCT customer_id 
    FROM orders 
    WHERE order_date > ADD_MONTHS(SYSDATE, -36)
)
AND created_date < ADD_MONTHS(SYSDATE, -48);

-- 删除重复数据(保留最新的)
DELETE FROM employees e1
WHERE e1.rowid > (
    SELECT MIN(e2.rowid)
    FROM employees e2
    WHERE e2.email = e1.email
);

-- 或者使用分析函数删除重复数据
DELETE FROM (
    SELECT rowid, 
           ROW_NUMBER() OVER (
               PARTITION BY email 
               ORDER BY hire_date DESC, employee_id DESC
           ) as rn
    FROM employees
)
WHERE rn > 1;

4.3 批量删除优化

-- 分批删除避免锁表
DECLARE
    v_batch_size NUMBER := 5000;
    v_total_deleted NUMBER := 0;
    v_batch_deleted NUMBER;
BEGIN
    LOOP
        DELETE FROM audit_log 
        WHERE log_date < ADD_MONTHS(SYSDATE, -12)
          AND ROWNUM <= v_batch_size;
        
        v_batch_deleted := SQL%ROWCOUNT;
        v_total_deleted := v_total_deleted + v_batch_deleted;
        
        COMMIT;
        
        EXIT WHEN v_batch_deleted = 0;
        
        -- 给其他会话一些处理时间
        DBMS_LOCK.SLEEP(0.1);
        
        DBMS_OUTPUT.PUT_LINE('Deleted ' || v_batch_deleted || ' rows');
    END LOOP;
    
    DBMS_OUTPUT.PUT_LINE('Total deleted: ' || v_total_deleted || ' rows');
END;
/

-- 使用TRUNCATE快速清空表(注意:不能回滚)
TRUNCATE TABLE temp_calculations;

-- 删除分区数据
ALTER TABLE sales_data DROP PARTITION sales_2020_q1;

-- 并行删除
ALTER SESSION ENABLE PARALLEL DML;
DELETE /*+ PARALLEL(old_transactions, 4) */ FROM old_transactions
WHERE transaction_date < ADD_MONTHS(SYSDATE, -84);
COMMIT;

5. MERGE语句详解

5.1 MERGE基础语法

MERGE就像是"智能搬家",能够根据情况决定是搬新家具还是更新现有家具:

-- 基本MERGE语法
MERGE INTO employees e
USING employee_updates eu
ON (e.employee_id = eu.employee_id)
WHEN MATCHED THEN
    UPDATE SET 
        salary = eu.new_salary,
        department_id = eu.new_department_id,
        modified_date = SYSDATE
WHEN NOT MATCHED THEN
    INSERT (employee_id, first_name, last_name, email, hire_date, salary, department_id)
    VALUES (eu.employee_id, eu.first_name, eu.last_name, eu.email, 
            SYSDATE, eu.new_salary, eu.new_department_id);

-- 带条件的MERGE
MERGE INTO products p
USING (
    SELECT product_id, new_price, supplier_id
    FROM product_price_updates
    WHERE effective_date = TRUNC(SYSDATE)
) ppu
ON (p.product_id = ppu.product_id)
WHEN MATCHED THEN
    UPDATE SET 
        unit_price = ppu.new_price,
        modified_date = SYSDATE
    WHERE p.supplier_id = ppu.supplier_id  -- 额外条件
WHEN NOT MATCHED THEN
    INSERT (product_id, unit_price, supplier_id, created_date)
    VALUES (ppu.product_id, ppu.new_price, ppu.supplier_id, SYSDATE)
    WHERE ppu.new_price > 0;  -- 插入条件

5.2 复杂MERGE应用

-- 数据仓库ETL场景
MERGE INTO fact_sales fs
USING (
    SELECT 
        s.sale_id,
        s.product_id,
        s.customer_id,
        s.sale_date,
        s.quantity,
        s.unit_price,
        s.total_amount,
        p.category_id,
        c.region_id,
        EXTRACT(YEAR FROM s.sale_date) as sale_year,
        EXTRACT(MONTH FROM s.sale_date) as sale_month
    FROM staging_sales s
    JOIN products p ON s.product_id = p.product_id
    JOIN customers c ON s.customer_id = c.customer_id
    WHERE s.processed_flag = 'N'
) stage
ON (fs.sale_id = stage.sale_id)
WHEN MATCHED THEN
    UPDATE SET 
        quantity = stage.quantity,
        unit_price = stage.unit_price,
        total_amount = stage.total_amount,
        last_updated = SYSDATE
    WHERE fs.total_amount != stage.total_amount  -- 只更新变化的记录
WHEN NOT MATCHED THEN
    INSERT (
        sale_id, product_id, customer_id, sale_date,
        quantity, unit_price, total_amount,
        category_id, region_id, sale_year, sale_month,
        created_date
    ) VALUES (
        stage.sale_id, stage.product_id, stage.customer_id, stage.sale_date,
        stage.quantity, stage.unit_price, stage.total_amount,
        stage.category_id, stage.region_id, stage.sale_year, stage.sale_month,
        SYSDATE
    );

-- 库存管理MERGE
MERGE INTO inventory i
USING (
    SELECT 
        product_id,
        SUM(CASE WHEN transaction_type = 'IN' THEN quantity ELSE -quantity END) as net_change
    FROM inventory_transactions
    WHERE transaction_date = TRUNC(SYSDATE)
      AND processed_flag = 'N'
    GROUP BY product_id
) it
ON (i.product_id = it.product_id)
WHEN MATCHED THEN
    UPDATE SET 
        quantity_on_hand = quantity_on_hand + it.net_change,
        last_updated = SYSDATE
    WHERE it.net_change != 0
WHEN NOT MATCHED THEN
    INSERT (product_id, quantity_on_hand, last_updated)
    VALUES (it.product_id, it.net_change, SYSDATE)
    WHERE it.net_change > 0;

-- 客户360度视图更新
MERGE INTO customer_360 c360
USING (
    WITH customer_stats AS (
        SELECT 
            customer_id,
            COUNT(*) as total_orders,
            SUM(total_amount) as total_spent,
            AVG(total_amount) as avg_order_value,
            MAX(order_date) as last_order_date,
            MIN(order_date) as first_order_date
        FROM orders
        WHERE order_date >= ADD_MONTHS(SYSDATE, -12)
        GROUP BY customer_id
    ),
    customer_segments AS (
        SELECT 
            customer_id,
            CASE 
                WHEN total_spent > 10000 THEN 'VIP'
                WHEN total_spent > 5000 THEN 'PREMIUM'
                WHEN total_spent > 1000 THEN 'REGULAR'
                ELSE 'BASIC'
            END as segment,
            CASE 
                WHEN last_order_date > SYSDATE - 30 THEN 'ACTIVE'
                WHEN last_order_date > SYSDATE - 90 THEN 'INACTIVE'
                ELSE 'DORMANT'
            END as status
        FROM customer_stats
    )
    SELECT 
        cs.customer_id,
        cs.total_orders,
        cs.total_spent,
        cs.avg_order_value,
        cs.last_order_date,
        cs.first_order_date,
        seg.segment,
        seg.status
    FROM customer_stats cs
    JOIN customer_segments seg ON cs.customer_id = seg.customer_id
) stats
ON (c360.customer_id = stats.customer_id)
WHEN MATCHED THEN
    UPDATE SET 
        total_orders = stats.total_orders,
        total_spent = stats.total_spent,
        avg_order_value = stats.avg_order_value,
        last_order_date = stats.last_order_date,
        customer_segment = stats.segment,
        customer_status = stats.status,
        last_updated = SYSDATE
WHEN NOT MATCHED THEN
    INSERT (
        customer_id, total_orders, total_spent, avg_order_value,
        last_order_date, first_order_date, customer_segment, customer_status,
        created_date, last_updated
    ) VALUES (
        stats.customer_id, stats.total_orders, stats.total_spent, stats.avg_order_value,
        stats.last_order_date, stats.first_order_date, stats.segment, stats.status,
        SYSDATE, SYSDATE
    );

6. 事务控制

6.1 事务基础

事务就像是一套组合拳,要么全部成功,要么全部失败:

Oracle事务控制
COMMIT提交
ROLLBACK回滚
SAVEPOINT保存点
事务隔离级别
永久保存更改
释放锁资源
撤销所有更改
回到事务开始
设置回滚点
部分回滚
READ COMMITTED
SERIALIZABLE
-- 基本事务控制
BEGIN
    INSERT INTO orders (order_id, customer_id, order_date, total_amount)
    VALUES (1001, 123, SYSDATE, 1500);
    
    INSERT INTO order_details (order_id, product_id, quantity, unit_price)
    VALUES (1001, 456, 2, 750);
    
    UPDATE inventory 
    SET quantity_on_hand = quantity_on_hand - 2
    WHERE product_id = 456;
    
    COMMIT;  -- 提交所有更改
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;  -- 出错时回滚
        RAISE;
END;
/

-- 使用保存点
DECLARE
    v_error_count NUMBER := 0;
BEGIN
    SAVEPOINT start_processing;
    
    -- 第一批操作
    INSERT INTO batch_log (batch_id, status) VALUES (1, 'STARTED');
    
    SAVEPOINT batch1_complete;
    
    -- 第二批操作
    BEGIN
        UPDATE products SET unit_price = unit_price * 1.1;
        INSERT INTO price_history (product_id, old_price, new_price, change_date)
        SELECT product_id, unit_price / 1.1, unit_price, SYSDATE FROM products;
        
        SAVEPOINT batch2_complete;
    EXCEPTION
        WHEN OTHERS THEN
            ROLLBACK TO batch1_complete;  -- 只回滚第二批操作
            v_error_count := v_error_count + 1;
    END;
    
    -- 第三批操作
    BEGIN
        DELETE FROM temp_calculations WHERE created_date < SYSDATE - 1;
        COMMIT;
    EXCEPTION
        WHEN OTHERS THEN
            ROLLBACK TO batch2_complete;
            v_error_count := v_error_count + 1;
    END;
    
    IF v_error_count > 0 THEN
        DBMS_OUTPUT.PUT_LINE('Completed with ' || v_error_count || ' errors');
    END IF;
END;
/

6.2 复杂事务处理

-- 银行转账事务示例
CREATE OR REPLACE PROCEDURE transfer_funds(
    p_from_account IN NUMBER,
    p_to_account IN NUMBER,
    p_amount IN NUMBER
)
IS
    v_from_balance NUMBER;
    v_to_balance NUMBER;
    insufficient_funds EXCEPTION;
    account_not_found EXCEPTION;
BEGIN
    SAVEPOINT before_transfer;
    
    -- 锁定源账户并检查余额
    SELECT balance INTO v_from_balance
    FROM accounts
    WHERE account_id = p_from_account
    FOR UPDATE;
    
    IF v_from_balance < p_amount THEN
        RAISE insufficient_funds;
    END IF;
    
    -- 锁定目标账户
    SELECT balance INTO v_to_balance
    FROM accounts
    WHERE account_id = p_to_account
    FOR UPDATE;
    
    -- 执行转账
    UPDATE accounts 
    SET balance = balance - p_amount,
        last_transaction_date = SYSDATE
    WHERE account_id = p_from_account;
    
    UPDATE accounts 
    SET balance = balance + p_amount,
        last_transaction_date = SYSDATE
    WHERE account_id = p_to_account;
    
    -- 记录交易历史
    INSERT INTO transactions (transaction_id, account_id, transaction_type, amount, balance_after)
    VALUES (trans_seq.NEXTVAL, p_from_account, 'DEBIT', -p_amount, v_from_balance - p_amount);
    
    INSERT INTO transactions (transaction_id, account_id, transaction_type, amount, balance_after)
    VALUES (trans_seq.NEXTVAL, p_to_account, 'CREDIT', p_amount, v_to_balance + p_amount);
    
    COMMIT;
    
    DBMS_OUTPUT.PUT_LINE('Transfer completed successfully');
    
EXCEPTION
    WHEN insufficient_funds THEN
        ROLLBACK TO before_transfer;
        RAISE_APPLICATION_ERROR(-20001, 'Insufficient funds');
    WHEN NO_DATA_FOUND THEN
        ROLLBACK TO before_transfer;
        RAISE_APPLICATION_ERROR(-20002, 'Account not found');
    WHEN OTHERS THEN
        ROLLBACK TO before_transfer;
        RAISE_APPLICATION_ERROR(-20003, 'Transfer failed: ' || SQLERRM);
END;
/

-- 批量处理事务
CREATE OR REPLACE PROCEDURE process_daily_orders
IS
    CURSOR order_cursor IS
        SELECT order_id, customer_id, total_amount
        FROM pending_orders
        WHERE order_date = TRUNC(SYSDATE)
        FOR UPDATE SKIP LOCKED;  -- 跳过被锁定的行
    
    v_processed_count NUMBER := 0;
    v_error_count NUMBER := 0;
    v_batch_size NUMBER := 100;
    
BEGIN
    FOR order_rec IN order_cursor LOOP
        BEGIN
            SAVEPOINT process_order;
            
            -- 处理订单逻辑
            UPDATE inventory 
            SET quantity_on_hand = quantity_on_hand - (
                SELECT SUM(quantity) 
                FROM order_details 
                WHERE order_id = order_rec.order_id
            )
            WHERE product_id IN (
                SELECT product_id 
                FROM order_details 
                WHERE order_id = order_rec.order_id
            );
            
            -- 更新订单状态
            UPDATE pending_orders 
            SET status = 'PROCESSED',
                processed_date = SYSDATE
            WHERE order_id = order_rec.order_id;
            
            v_processed_count := v_processed_count + 1;
            
            -- 批量提交
            IF MOD(v_processed_count, v_batch_size) = 0 THEN
                COMMIT;
                DBMS_OUTPUT.PUT_LINE('Processed ' || v_processed_count || ' orders');
            END IF;
            
        EXCEPTION
            WHEN OTHERS THEN
                ROLLBACK TO process_order;
                v_error_count := v_error_count + 1;
                
                -- 记录错误
                INSERT INTO error_log (error_date, error_message, order_id)
                VALUES (SYSDATE, SQLERRM, order_rec.order_id);
        END;
    END LOOP;
    
    COMMIT;  -- 提交剩余的更改
    
    DBMS_OUTPUT.PUT_LINE('Processing complete: ' || v_processed_count || 
                        ' processed, ' || v_error_count || ' errors');
END;
/

7. 性能优化技巧

7.1 批量操作优化

DML性能优化策略
批量操作
索引优化
并行处理
分区策略
FORALL批量DML
BULK COLLECT
MERGE语句
合适的索引
避免索引失效
函数索引
并行DML
并行查询
分区并行
分区消除
分区智能连接
分区索引
-- 高效的批量插入
DECLARE
    TYPE number_array IS TABLE OF NUMBER;
    TYPE varchar_array IS TABLE OF VARCHAR2(100);
    TYPE date_array IS TABLE OF DATE;
    
    l_emp_ids number_array;
    l_names varchar_array;
    l_emails varchar_array;
    l_hire_dates date_array;
    
    v_batch_size NUMBER := 10000;
BEGIN
    -- 准备数据
    SELECT employee_id, first_name || ' ' || last_name, email, hire_date
    BULK COLLECT INTO l_emp_ids, l_names, l_emails, l_hire_dates
    FROM external_employees
    WHERE import_flag = 'Y';
    
    -- 批量插入
    FORALL i IN 1..l_emp_ids.COUNT SAVE EXCEPTIONS
        INSERT INTO employees (employee_id, full_name, email, hire_date)
        VALUES (l_emp_ids(i), l_names(i), l_emails(i), l_hire_dates(i));
    
    COMMIT;
    
    DBMS_OUTPUT.PUT_LINE('Inserted ' || l_emp_ids.COUNT || ' employees');
    
EXCEPTION
    WHEN OTHERS THEN
        IF SQLCODE = -24381 THEN  -- FORALL with SAVE EXCEPTIONS
            FOR i IN 1..SQL%BULK_EXCEPTIONS.COUNT LOOP
                DBMS_OUTPUT.PUT_LINE('Error ' || i || ': ' || 
                    SQL%BULK_EXCEPTIONS(i).ERROR_CODE || ' at index ' ||
                    SQL%BULK_EXCEPTIONS(i).ERROR_INDEX);
            END LOOP;
        END IF;
        RAISE;
END;
/

-- 优化的大表更新
CREATE OR REPLACE PROCEDURE optimize_large_table_update
IS
    CURSOR update_cursor IS
        SELECT rowid, employee_id, salary
        FROM employees
        WHERE last_review_date < ADD_MONTHS(SYSDATE, -12)
          AND status = 'ACTIVE';
    
    TYPE rowid_array IS TABLE OF ROWID;
    TYPE number_array IS TABLE OF NUMBER;
    
    l_rowids rowid_array;
    l_new_salaries number_array;
    
    v_batch_size NUMBER := 5000;
    v_total_updated NUMBER := 0;
BEGIN
    OPEN update_cursor;
    
    LOOP
        FETCH update_cursor BULK COLLECT INTO l_rowids, l_new_salaries
        LIMIT v_batch_size;
        
        EXIT WHEN l_rowids.COUNT = 0;
        
        -- 计算新薪资
        FOR i IN 1..l_rowids.COUNT LOOP
            l_new_salaries(i) := l_new_salaries(i) * 1.05;
        END LOOP;
        
        -- 批量更新
        FORALL i IN 1..l_rowids.COUNT
            UPDATE employees
            SET salary = l_new_salaries(i),
                last_review_date = SYSDATE
            WHERE rowid = l_rowids(i);
        
        v_total_updated := v_total_updated + l_rowids.COUNT;
        COMMIT;
        
        DBMS_OUTPUT.PUT_LINE('Updated ' || l_rowids.COUNT || ' rows');
    END LOOP;
    
    CLOSE update_cursor;
    
    DBMS_OUTPUT.PUT_LINE('Total updated: ' || v_total_updated || ' rows');
END;
/

7.2 索引和查询优化

-- 使用提示优化DML
-- 强制使用特定索引
UPDATE /*+ INDEX(employees, idx_emp_dept_id) */ employees
SET salary = salary * 1.1
WHERE department_id = 20;

-- 并行DML
ALTER SESSION ENABLE PARALLEL DML;
INSERT /*+ PARALLEL(sales_archive, 4) */ INTO sales_archive
SELECT * FROM sales_data WHERE sale_date < ADD_MONTHS(SYSDATE, -36);

-- 使用APPEND提示减少日志
INSERT /*+ APPEND */ INTO employees_backup
SELECT * FROM employees WHERE status = 'INACTIVE';

-- 优化MERGE性能
MERGE /*+ USE_HASH(target source) */ INTO customer_summary target
USING (
    SELECT /*+ PARALLEL(orders, 2) */
        customer_id,
        COUNT(*) as order_count,
        SUM(total_amount) as total_spent
    FROM orders
    WHERE order_date >= ADD_MONTHS(SYSDATE, -12)
    GROUP BY customer_id
) source
ON (target.customer_id = source.customer_id)
WHEN MATCHED THEN
    UPDATE SET 
        order_count = source.order_count,
        total_spent = source.total_spent,
        last_updated = SYSDATE
WHEN NOT MATCHED THEN
    INSERT (customer_id, order_count, total_spent, last_updated)
    VALUES (source.customer_id, source.order_count, source.total_spent, SYSDATE);

-- 分区智能操作
-- 只操作相关分区
INSERT INTO sales_data PARTITION (sales_2024_q1)
SELECT * FROM staging_sales 
WHERE sale_date BETWEEN DATE '2024-01-01' AND DATE '2024-03-31';

-- 分区交换(快速数据移动)
ALTER TABLE sales_data EXCHANGE PARTITION sales_2023_q1 
WITH TABLE sales_2023_q1_archive;

结语
感谢您的阅读!期待您的一键三连!欢迎指正!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Guiat

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值