【Oracle】分区表

发布于:2025-06-06 ⋅ 阅读:(20) ⋅ 点赞:(0)

在这里插入图片描述

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

在这里插入图片描述

正文

1. 分区表基础概述

分区表是Oracle数据库中将大表物理分割成多个较小、更易管理的片段的技术。每个分区可以独立管理,同时对应用程序保持透明。

1.1 分区表的概念与优势

Oracle分区表
性能优势
管理优势
可用性优势
存储优势
分区消除
并行处理
分区连接
索引优化
独立维护
分区交换
在线重定义
统计信息管理
分区级备份
故障隔离
在线操作
快速恢复
数据压缩
存储分层
空间管理
归档策略

1.2 分区类型概览

Oracle分区类型
单级分区
复合分区
范围分区 RANGE
列表分区 LIST
哈希分区 HASH
间隔分区 INTERVAL
引用分区 REFERENCE
虚拟列分区 VIRTUAL COLUMN
范围-哈希 RANGE-HASH
范围-列表 RANGE-LIST
列表-哈希 LIST-HASH
列表-列表 LIST-LIST
范围-范围 RANGE-RANGE
间隔-哈希 INTERVAL-HASH

1.3 分区表的工作原理

SQL查询
分区剪枝
确定目标分区
并行执行
结果合并
返回结果
分区键
分区函数
分区映射
物理存储

2. 范围分区 (RANGE Partitioning)

2.1 基础范围分区

2.1.1 按日期范围分区

-- 创建按日期范围分区的销售表
CREATE TABLE sales_range_date (
    sale_id NUMBER,
    customer_id NUMBER,
    product_id NUMBER,
    sale_date DATE,
    amount NUMBER(10,2),
    quantity NUMBER,
    sales_rep_id NUMBER
)
PARTITION BY RANGE (sale_date) (
    PARTITION sales_2020 VALUES LESS THAN (DATE '2021-01-01'),
    PARTITION sales_2021 VALUES LESS THAN (DATE '2022-01-01'),
    PARTITION sales_2022 VALUES LESS THAN (DATE '2023-01-01'),
    PARTITION sales_2023 VALUES LESS THAN (DATE '2024-01-01'),
    PARTITION sales_future VALUES LESS THAN (MAXVALUE)
);

-- 创建分区表的索引
CREATE INDEX idx_sales_range_customer ON sales_range_date (customer_id) LOCAL;
CREATE INDEX idx_sales_range_product ON sales_range_date (product_id) LOCAL;

-- 插入测试数据
INSERT INTO sales_range_date VALUES (1, 1001, 2001, DATE '2020-03-15', 1500.00, 3, 501);
INSERT INTO sales_range_date VALUES (2, 1002, 2002, DATE '2021-06-20', 2300.50, 5, 502);
INSERT INTO sales_range_date VALUES (3, 1003, 2003, DATE '2022-09-10', 890.75, 2, 503);
INSERT INTO sales_range_date VALUES (4, 1004, 2004, DATE '2023-12-05', 3200.00, 8, 504);
INSERT INTO sales_range_date VALUES (5, 1005, 2005, DATE '2024-02-14', 1750.25, 4, 505);

COMMIT;

-- 查看分区信息
SELECT table_name, partition_name, high_value, num_rows, blocks
FROM user_tab_partitions 
WHERE table_name = 'SALES_RANGE_DATE'
ORDER BY partition_position;

-- 演示分区消除
EXPLAIN PLAN FOR
SELECT * FROM sales_range_date 
WHERE sale_date BETWEEN DATE '2022-01-01' AND DATE '2022-12-31';

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

2.1.2 按数值范围分区

-- 创建按员工ID范围分区的员工表
CREATE TABLE employees_range_id (
    employee_id NUMBER,
    first_name VARCHAR2(50),
    last_name VARCHAR2(50),
    email VARCHAR2(100),
    hire_date DATE,
    salary NUMBER(8,2),
    department_id NUMBER
)
PARTITION BY RANGE (employee_id) (
    PARTITION emp_1_1000 VALUES LESS THAN (1001),
    PARTITION emp_1001_2000 VALUES LESS THAN (2001),
    PARTITION emp_2001_3000 VALUES LESS THAN (3001),
    PARTITION emp_3001_4000 VALUES LESS THAN (4001),
    PARTITION emp_others VALUES LESS THAN (MAXVALUE)
);

-- 插入测试数据
INSERT INTO employees_range_id VALUES (500, 'John', 'Doe', 'john.doe@company.com', SYSDATE, 5000, 10);
INSERT INTO employees_range_id VALUES (1500, 'Jane', 'Smith', 'jane.smith@company.com', SYSDATE, 6000, 20);
INSERT INTO employees_range_id VALUES (2500, 'Bob', 'Johnson', 'bob.johnson@company.com', SYSDATE, 5500, 30);
INSERT INTO employees_range_id VALUES (3500, 'Alice', 'Brown', 'alice.brown@company.com', SYSDATE, 7000, 40);
INSERT INTO employees_range_id VALUES (5000, 'Mike', 'Wilson', 'mike.wilson@company.com', SYSDATE, 8000, 50);

COMMIT;

-- 查看数据分布
SELECT 'emp_1_1000' as partition_name, COUNT(*) as row_count FROM employees_range_id PARTITION(emp_1_1000)
UNION ALL
SELECT 'emp_1001_2000', COUNT(*) FROM employees_range_id PARTITION(emp_1001_2000)
UNION ALL
SELECT 'emp_2001_3000', COUNT(*) FROM employees_range_id PARTITION(emp_2001_3000)
UNION ALL
SELECT 'emp_3001_4000', COUNT(*) FROM employees_range_id PARTITION(emp_3001_4000)
UNION ALL
SELECT 'emp_others', COUNT(*) FROM employees_range_id PARTITION(emp_others);

2.2 间隔分区 (INTERVAL Partitioning)

2.2.1 自动创建月度分区

-- 创建间隔分区表(按月自动分区)
CREATE TABLE sales_interval_monthly (
    sale_id NUMBER,
    customer_id NUMBER,
    product_id NUMBER,
    sale_date DATE,
    amount NUMBER(10,2),
    quantity NUMBER
)
PARTITION BY RANGE (sale_date)
INTERVAL (NUMTOYMINTERVAL(1, 'MONTH'))
(
    PARTITION sales_initial VALUES LESS THAN (DATE '2023-01-01')
);

-- 创建本地索引
CREATE INDEX idx_sales_interval_customer ON sales_interval_monthly (customer_id) LOCAL;

-- 插入跨多个月的数据,观察自动分区创建
INSERT INTO sales_interval_monthly VALUES (1, 1001, 2001, DATE '2022-12-15', 1500.00, 3);
INSERT INTO sales_interval_monthly VALUES (2, 1002, 2002, DATE '2023-01-20', 2300.50, 5);
INSERT INTO sales_interval_monthly VALUES (3, 1003, 2003, DATE '2023-02-10', 890.75, 2);
INSERT INTO sales_interval_monthly VALUES (4, 1004, 2004, DATE '2023-03-05', 3200.00, 8);
INSERT INTO sales_interval_monthly VALUES (5, 1005, 2005, DATE '2023-04-14', 1750.25, 4);
INSERT INTO sales_interval_monthly VALUES (6, 1006, 2006, DATE '2023-05-22', 2100.00, 6);

COMMIT;

-- 查看自动创建的分区
SELECT table_name, partition_name, high_value, interval
FROM user_tab_partitions 
WHERE table_name = 'SALES_INTERVAL_MONTHLY'
ORDER BY partition_position;

-- 创建间隔分区表(按天自动分区)
CREATE TABLE transaction_log_daily (
    transaction_id NUMBER,
    user_id NUMBER,
    transaction_type VARCHAR2(20),
    transaction_date DATE,
    amount NUMBER(12,2),
    description VARCHAR2(200)
)
PARTITION BY RANGE (transaction_date)
INTERVAL (NUMTODSINTERVAL(1, 'DAY'))
(
    PARTITION trans_initial VALUES LESS THAN (DATE '2024-01-01')
);

-- 插入跨多天的数据
INSERT INTO transaction_log_daily VALUES (1, 1001, 'PURCHASE', DATE '2023-12-30', 150.00, 'Online purchase');
INSERT INTO transaction_log_daily VALUES (2, 1002, 'REFUND', DATE '2024-01-01', -50.00, 'Product return');
INSERT INTO transaction_log_daily VALUES (3, 1003, 'PURCHASE', DATE '2024-01-02', 300.00, 'Store purchase');
INSERT INTO transaction_log_daily VALUES (4, 1004, 'TRANSFER', DATE '2024-01-03', 1000.00, 'Account transfer');

COMMIT;

-- 查看每日分区
SELECT partition_name, high_value, num_rows
FROM user_tab_partitions 
WHERE table_name = 'TRANSACTION_LOG_DAILY'
ORDER BY partition_position;

2.2.2 间隔分区的管理操作

-- 创建间隔分区管理存储过程
CREATE OR REPLACE PROCEDURE manage_interval_partitions(
    p_table_name IN VARCHAR2,
    p_keep_months IN NUMBER DEFAULT 12
)
AS
    v_sql VARCHAR2(4000);
    v_partition_count NUMBER := 0;
    v_dropped_count NUMBER := 0;
    v_cutoff_date DATE;
BEGIN
    DBMS_OUTPUT.PUT_LINE('=== 间隔分区管理 ===');
    DBMS_OUTPUT.PUT_LINE('表名: ' || p_table_name);
    DBMS_OUTPUT.PUT_LINE('保留月数: ' || p_keep_months);
    
    -- 计算截止日期
    v_cutoff_date := ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -p_keep_months);
    DBMS_OUTPUT.PUT_LINE('删除截止日期: ' || TO_CHAR(v_cutoff_date, 'YYYY-MM-DD'));
    DBMS_OUTPUT.PUT_LINE('');
    
    -- 查询需要删除的分区
    FOR rec IN (
        SELECT partition_name, high_value
        FROM user_tab_partitions
        WHERE table_name = UPPER(p_table_name)
          AND interval = 'YES'
        ORDER BY partition_position
    ) LOOP
        v_partition_count := v_partition_count + 1;
        
        -- 解析high_value中的日期
        DECLARE
            v_high_date DATE;
            v_high_value_str VARCHAR2(4000);
        BEGIN
            -- 获取分区的上界值
            SELECT high_value INTO v_high_value_str
            FROM user_tab_partitions
            WHERE table_name = UPPER(p_table_name)
              AND partition_name = rec.partition_name;
            
            -- 执行动态SQL获取日期值
            EXECUTE IMMEDIATE 'SELECT ' || v_high_value_str || ' FROM dual' INTO v_high_date;
            
            DBMS_OUTPUT.PUT_LINE('分区: ' || rec.partition_name || 
                               ', 上界: ' || TO_CHAR(v_high_date, 'YYYY-MM-DD'));
            
            -- 如果分区太旧,删除它
            IF v_high_date <= v_cutoff_date THEN
                v_sql := 'ALTER TABLE ' || p_table_name || ' DROP PARTITION ' || rec.partition_name;
                EXECUTE IMMEDIATE v_sql;
                v_dropped_count := v_dropped_count + 1;
                DBMS_OUTPUT.PUT_LINE('  -> 已删除');
            ELSE
                DBMS_OUTPUT.PUT_LINE('  -> 保留');
            END IF;
            
        EXCEPTION
            WHEN OTHERS THEN
                DBMS_OUTPUT.PUT_LINE('  -> 处理错误: ' || SQLERRM);
        END;
    END LOOP;
    
    DBMS_OUTPUT.PUT_LINE('');
    DBMS_OUTPUT.PUT_LINE('=== 管理完成 ===');
    DBMS_OUTPUT.PUT_LINE('检查分区数: ' || v_partition_count);
    DBMS_OUTPUT.PUT_LINE('删除分区数: ' || v_dropped_count);
    DBMS_OUTPUT.PUT_LINE('保留分区数: ' || (v_partition_count - v_dropped_count));
    
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('管理过程出错: ' || SQLERRM);
END;
/

-- 测试分区管理
EXEC manage_interval_partitions('SALES_INTERVAL_MONTHLY', 6);

3. 列表分区 (LIST Partitioning)

3.1 基础列表分区

3.1.1 按地区分区

-- 创建按地区列表分区的客户表
CREATE TABLE customers_list_region (
    customer_id NUMBER,
    customer_name VARCHAR2(100),
    email VARCHAR2(100),
    phone VARCHAR2(20),
    region VARCHAR2(20),
    country VARCHAR2(50),
    registration_date DATE,
    status VARCHAR2(20)
)
PARTITION BY LIST (region) (
    PARTITION customers_north VALUES ('NORTH', 'NORTHEAST', 'NORTHWEST'),
    PARTITION customers_south VALUES ('SOUTH', 'SOUTHEAST', 'SOUTHWEST'),
    PARTITION customers_east VALUES ('EAST', 'CENTRAL_EAST'),
    PARTITION customers_west VALUES ('WEST', 'CENTRAL_WEST'),
    PARTITION customers_international VALUES ('INTERNATIONAL', 'OVERSEAS'),
    PARTITION customers_default VALUES (DEFAULT)
);

-- 插入测试数据
INSERT INTO customers_list_region VALUES (1, 'ABC Corp', 'contact@abc.com', '555-0001', 'NORTH', 'USA', SYSDATE, 'ACTIVE');
INSERT INTO customers_list_region VALUES (2, 'XYZ Ltd', 'info@xyz.com', '555-0002', 'SOUTH', 'USA', SYSDATE, 'ACTIVE');
INSERT INTO customers_list_region VALUES (3, 'Global Inc', 'sales@global.com', '555-0003', 'INTERNATIONAL', 'UK', SYSDATE, 'ACTIVE');
INSERT INTO customers_list_region VALUES (4, 'Local Store', 'hello@local.com', '555-0004', 'EAST', 'USA', SYSDATE, 'INACTIVE');
INSERT INTO customers_list_region VALUES (5, 'Unknown Co', 'contact@unknown.com', '555-0005', 'OTHER', 'CANADA', SYSDATE, 'PENDING');

COMMIT;

-- 查看数据分布
SELECT 
    p.partition_name,
    p.high_value,
    NVL(s.num_rows, 0) as row_count
FROM user_tab_partitions p
LEFT JOIN user_tab_statistics s ON p.table_name = s.table_name AND p.partition_name = s.partition_name
WHERE p.table_name = 'CUSTOMERS_LIST_REGION'
ORDER BY p.partition_position;

-- 查询特定分区的数据
SELECT * FROM customers_list_region PARTITION(customers_north);
SELECT * FROM customers_list_region PARTITION(customers_default);

3.1.2 按状态分区

-- 创建按订单状态分区的订单表
CREATE TABLE orders_list_status (
    order_id NUMBER,
    customer_id NUMBER,
    order_date DATE,
    total_amount NUMBER(10,2),
    order_status VARCHAR2(20),
    payment_method VARCHAR2(20),
    shipping_address VARCHAR2(200)
)
PARTITION BY LIST (order_status) (
    PARTITION orders_pending VALUES ('PENDING', 'SUBMITTED', 'PROCESSING'),
    PARTITION orders_confirmed VALUES ('CONFIRMED', 'PAID', 'PREPARING'),
    PARTITION orders_shipped VALUES ('SHIPPED', 'IN_TRANSIT', 'OUT_FOR_DELIVERY'),
    PARTITION orders_completed VALUES ('DELIVERED', 'COMPLETED'),
    PARTITION orders_cancelled VALUES ('CANCELLED', 'REFUNDED', 'RETURNED'),
    PARTITION orders_other VALUES (DEFAULT)
);

-- 插入测试数据
INSERT INTO orders_list_status VALUES (1001, 1, SYSDATE-5, 150.00, 'PENDING', 'CREDIT_CARD', '123 Main St');
INSERT INTO orders_list_status VALUES (1002, 2, SYSDATE-4, 250.50, 'CONFIRMED', 'PAYPAL', '456 Oak Ave');
INSERT INTO orders_list_status VALUES (1003, 3, SYSDATE-3, 89.99, 'SHIPPED', 'DEBIT_CARD', '789 Pine Rd');
INSERT INTO orders_list_status VALUES (1004, 4, SYSDATE-2, 320.00, 'DELIVERED', 'CASH', '321 Elm St');
INSERT INTO orders_list_status VALUES (1005, 5, SYSDATE-1, 175.25, 'CANCELLED', 'CREDIT_CARD', '654 Maple Dr');
INSERT INTO orders_list_status VALUES (1006, 1, SYSDATE, 99.99, 'UNKNOWN', 'BITCOIN', '987 Cedar Ln');

COMMIT;

-- 创建订单状态统计视图
CREATE OR REPLACE VIEW order_status_summary AS
SELECT 
    CASE 
        WHEN partition_name = 'ORDERS_PENDING' THEN 'Pending Orders'
        WHEN partition_name = 'ORDERS_CONFIRMED' THEN 'Confirmed Orders'
        WHEN partition_name = 'ORDERS_SHIPPED' THEN 'Shipped Orders'
        WHEN partition_name = 'ORDERS_COMPLETED' THEN 'Completed Orders'
        WHEN partition_name = 'ORDERS_CANCELLED' THEN 'Cancelled Orders'
        ELSE 'Other Status'
    END as status_category,
    COUNT(*) as order_count,
    SUM(total_amount) as total_value
FROM (
    SELECT 'ORDERS_PENDING' as partition_name, total_amount FROM orders_list_status PARTITION(orders_pending)
    UNION ALL
    SELECT 'ORDERS_CONFIRMED', total_amount FROM orders_list_status PARTITION(orders_confirmed)
    UNION ALL
    SELECT 'ORDERS_SHIPPED', total_amount FROM orders_list_status PARTITION(orders_shipped)
    UNION ALL
    SELECT 'ORDERS_COMPLETED', total_amount FROM orders_list_status PARTITION(orders_completed)
    UNION ALL
    SELECT 'ORDERS_CANCELLED', total_amount FROM orders_list_status PARTITION(orders_cancelled)
    UNION ALL
    SELECT 'ORDERS_OTHER', total_amount FROM orders_list_status PARTITION(orders_other)
)
GROUP BY partition_name
ORDER BY order_count DESC;

-- 查看订单状态汇总
SELECT * FROM order_status_summary;

3.2 列表分区的动态管理

3.2.1 分区值管理

-- 创建列表分区管理存储过程
CREATE OR REPLACE PROCEDURE manage_list_partition_values(
    p_table_name IN VARCHAR2,
    p_partition_name IN VARCHAR2,
    p_action IN VARCHAR2, -- 'ADD' or 'DROP'
    p_values IN VARCHAR2  -- 逗号分隔的值列表
)
AS
    v_sql VARCHAR2(4000);
    v_current_values CLOB;
    TYPE value_array IS TABLE OF VARCHAR2(100);
    v_values_to_process value_array;
    v_value VARCHAR2(100);
    v_pos NUMBER;
    v_remaining VARCHAR2(4000);
BEGIN
    DBMS_OUTPUT.PUT_LINE('=== 列表分区值管理 ===');
    DBMS_OUTPUT.PUT_LINE('表名: ' || p_table_name);
    DBMS_OUTPUT.PUT_LINE('分区名: ' || p_partition_name);
    DBMS_OUTPUT.PUT_LINE('操作: ' || p_action);
    DBMS_OUTPUT.PUT_LINE('值: ' || p_values);
    
    -- 获取当前分区值
    SELECT high_value INTO v_current_values
    FROM user_tab_partitions
    WHERE table_name = UPPER(p_table_name)
      AND partition_name = UPPER(p_partition_name);
    
    DBMS_OUTPUT.PUT_LINE('当前分区值: ' || v_current_values);
    
    -- 解析输入的值列表
    v_remaining := p_values;
    v_values_to_process := value_array();
    
    WHILE LENGTH(v_remaining) > 0 LOOP
        v_pos := INSTR(v_remaining, ',');
        IF v_pos > 0 THEN
            v_value := TRIM(SUBSTR(v_remaining, 1, v_pos - 1));
            v_remaining := SUBSTR(v_remaining, v_pos + 1);
        ELSE
            v_value := TRIM(v_remaining);
            v_remaining := '';
        END IF;
        
        v_values_to_process.EXTEND;
        v_values_to_process(v_values_to_process.COUNT) := v_value;
    END LOOP;
    
    -- 执行操作
    IF UPPER(p_action) = 'ADD' THEN
        FOR i IN 1..v_values_to_process.COUNT LOOP
            v_sql := 'ALTER TABLE ' || p_table_name || 
                     ' MODIFY PARTITION ' || p_partition_name || 
                     ' ADD VALUES (''' || v_values_to_process(i) || ''')';
            
            BEGIN
                EXECUTE IMMEDIATE v_sql;
                DBMS_OUTPUT.PUT_LINE('已添加值: ' || v_values_to_process(i));
            EXCEPTION
                WHEN OTHERS THEN
                    DBMS_OUTPUT.PUT_LINE('添加值失败 ' || v_values_to_process(i) || ': ' || SQLERRM);
            END;
        END LOOP;
        
    ELSIF UPPER(p_action) = 'DROP' THEN
        FOR i IN 1..v_values_to_process.COUNT LOOP
            v_sql := 'ALTER TABLE ' || p_table_name || 
                     ' MODIFY PARTITION ' || p_partition_name || 
                     ' DROP VALUES (''' || v_values_to_process(i) || ''')';
            
            BEGIN
                EXECUTE IMMEDIATE v_sql;
                DBMS_OUTPUT.PUT_LINE('已删除值: ' || v_values_to_process(i));
            EXCEPTION
                WHEN OTHERS THEN
                    DBMS_OUTPUT.PUT_LINE('删除值失败 ' || v_values_to_process(i) || ': ' || SQLERRM);
            END;
        END LOOP;
    ELSE
        RAISE_APPLICATION_ERROR(-20001, '无效的操作类型: ' || p_action);
    END IF;
    
    -- 显示更新后的分区值
    SELECT high_value INTO v_current_values
    FROM user_tab_partitions
    WHERE table_name = UPPER(p_table_name)
      AND partition_name = UPPER(p_partition_name);
    
    DBMS_OUTPUT.PUT_LINE('更新后分区值: ' || v_current_values);
    
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('操作失败: ' || SQLERRM);
END;
/

-- 测试分区值管理
-- 为customers_north分区添加新的地区值
EXEC manage_list_partition_values('CUSTOMERS_LIST_REGION', 'CUSTOMERS_NORTH', 'ADD', 'NORTH_CENTRAL,UPPER_NORTH');

-- 查看更新后的分区信息
SELECT partition_name, high_value
FROM user_tab_partitions
WHERE table_name = 'CUSTOMERS_LIST_REGION'
  AND partition_name = 'CUSTOMERS_NORTH';

4. 哈希分区 (HASH Partitioning)

4.1 基础哈希分区

4.1.1 均匀数据分布

-- 创建哈希分区表用于均匀分布数据
CREATE TABLE products_hash (
    product_id NUMBER,
    product_name VARCHAR2(100),
    category_id NUMBER,
    price NUMBER(10,2),
    supplier_id NUMBER,
    created_date DATE,
    status VARCHAR2(20)
)
PARTITION BY HASH (product_id)
PARTITIONS 8;

-- 创建本地索引
CREATE INDEX idx_products_hash_category ON products_hash (category_id) LOCAL;
CREATE INDEX idx_products_hash_supplier ON products_hash (supplier_id) LOCAL;

-- 批量插入测试数据
DECLARE
    v_categories NUMBER := 10;
    v_suppliers NUMBER := 20;
    v_statuses SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST('ACTIVE', 'INACTIVE', 'DISCONTINUED', 'PENDING');
BEGIN
    FOR i IN 1..10000 LOOP
        INSERT INTO products_hash VALUES (
            i,
            'Product ' || i,
            MOD(i, v_categories) + 1,
            ROUND(DBMS_RANDOM.VALUE(10, 1000), 2),
            MOD(i, v_suppliers) + 1,
            SYSDATE - DBMS_RANDOM.VALUE(0, 365),
            v_statuses(MOD(i, 4) + 1)
        );
        
        -- 每1000条提交一次
        IF MOD(i, 1000) = 0 THEN
            COMMIT;
        END IF;
    END LOOP;
    COMMIT;
END;
/

-- 查看哈希分区的数据分布
SELECT 
    partition_name,
    num_rows,
    blocks,
    avg_row_len
FROM user_tab_partitions
WHERE table_name = 'PRODUCTS_HASH'
ORDER BY partition_name;

-- 收集统计信息
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'PRODUCTS_HASH');

-- 再次查看分布(收集统计信息后)
SELECT 
    partition_name,
    num_rows,
    ROUND(num_rows * 100.0 / SUM(num_rows) OVER(), 2) as percentage,
    blocks
FROM user_tab_partitions
WHERE table_name = 'PRODUCTS_HASH'
ORDER BY partition_name;

4.1.2 多列哈希分区

-- 创建基于多列的哈希分区表
CREATE TABLE user_activities_hash (
    user_id NUMBER,
    activity_date DATE,
    activity_type VARCHAR2(50),
    session_id VARCHAR2(100),
    duration_minutes NUMBER,
    page_views NUMBER,
    device_type VARCHAR2(20)
)
PARTITION BY HASH (user_id, activity_date)
PARTITIONS 16;

-- 插入测试数据
DECLARE
    v_activity_types SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(
        'LOGIN', 'BROWSE', 'SEARCH', 'PURCHASE', 'LOGOUT', 'DOWNLOAD', 'UPLOAD'
    );
    v_device_types SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(
        'DESKTOP', 'MOBILE', 'TABLET'
    );
BEGIN
    FOR i IN 1..50000 LOOP
        INSERT INTO user_activities_hash VALUES (
            TRUNC(DBMS_RANDOM.VALUE(1, 5000)),
            SYSDATE - DBMS_RANDOM.VALUE(0, 30),
            v_activity_types(TRUNC(DBMS_RANDOM.VALUE(1, 8))),
            'SESSION_' || LPAD(i, 8, '0'),
            TRUNC(DBMS_RANDOM.VALUE(1, 120)),
            TRUNC(DBMS_RANDOM.VALUE(1, 50)),
            v_device_types(TRUNC(DBMS_RANDOM.VALUE(1, 4)))
        );
        
        IF MOD(i, 5000) = 0 THEN
            COMMIT;
            DBMS_OUTPUT.PUT_LINE('已插入 ' || i || ' 条记录');
        END IF;
    END LOOP;
    COMMIT;
END;
/

-- 收集统计信息
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'USER_ACTIVITIES_HASH');

-- 分析哈希分区的均匀性
SELECT 
    partition_name,
    num_rows,
    ROUND(num_rows * 100.0 / SUM(num_rows) OVER(), 2) as percentage,
    blocks,
    avg_row_len
FROM user_tab_partitions
WHERE table_name = 'USER_ACTIVITIES_HASH'
ORDER BY num_rows DESC;

-- 计算分布的标准差(衡量均匀性)
WITH partition_stats AS (
    SELECT num_rows
    FROM user_tab_partitions
    WHERE table_name = 'USER_ACTIVITIES_HASH'
)
SELECT 
    ROUND(AVG(num_rows), 2) as avg_rows_per_partition,
    ROUND(STDDEV(num_rows), 2) as stddev_rows,
    ROUND(STDDEV(num_rows) / AVG(num_rows) * 100, 2) as coefficient_of_variation
FROM partition_stats;

4.2 哈希分区的性能优化

4.2.1 并行查询优化

-- 创建并行查询测试存储过程
CREATE OR REPLACE PROCEDURE test_hash_partition_performance(
    p_parallel_degree IN NUMBER DEFAULT 4
)
AS
    v_start_time TIMESTAMP;
    v_end_time TIMESTAMP;
    v_elapsed_seconds NUMBER;
    v_result_count NUMBER;
    v_total_amount NUMBER;
BEGIN
    DBMS_OUTPUT.PUT_LINE('=== 哈希分区性能测试 ===');
    DBMS_OUTPUT.PUT_LINE('并行度: ' || p_parallel_degree);
    
    -- 设置并行度
    EXECUTE IMMEDIATE 'ALTER TABLE products_hash PARALLEL ' || p_parallel_degree;
    
    -- 测试1: 全表扫描聚合查询
    DBMS_OUTPUT.PUT_LINE('');
    DBMS_OUTPUT.PUT_LINE('测试1: 全表聚合查询');
    v_start_time := SYSTIMESTAMP;
    
    SELECT COUNT(*), SUM(price)
    INTO v_result_count, v_total_amount
    FROM products_hash
    WHERE status = 'ACTIVE';
    
    v_end_time := SYSTIMESTAMP;
    v_elapsed_seconds := EXTRACT(SECOND FROM (v_end_time - v_start_time));
    
    DBMS_OUTPUT.PUT_LINE('结果: ' || v_result_count || ' 行, 总金额: ' || ROUND(v_total_amount, 2));
    DBMS_OUTPUT.PUT_LINE('执行时间: ' || v_elapsed_seconds || ' 秒');
    
    -- 测试2: 分区连接查询
    DBMS_OUTPUT.PUT_LINE('');
    DBMS_OUTPUT.PUT_LINE('测试2: 分区连接查询');
    v_start_time := SYSTIMESTAMP;
    
    SELECT COUNT(*)
    INTO v_result_count
    FROM products_hash p
    JOIN user_activities_hash u ON MOD(p.product_id, 1000) = MOD(u.user_id, 1000)
    WHERE p.status = 'ACTIVE'
      AND u.activity_type = 'PURCHASE';
    
    v_end_time := SYSTIMESTAMP;
    v_elapsed_seconds := EXTRACT(SECOND FROM (v_end_time - v_start_time));
    
    DBMS_OUTPUT.PUT_LINE('连接结果: ' || v_result_count || ' 行');
    DBMS_OUTPUT.PUT_LINE('执行时间: ' || v_elapsed_seconds || ' 秒');
    
    -- 测试3: 分区级别的统计
    DBMS_OUTPUT.PUT_LINE('');
    DBMS_OUTPUT.PUT_LINE('测试3: 分区级别统计');
    v_start_time := SYSTIMESTAMP;
    
    FOR rec IN (
        SELECT 
            'SYS_P' || ROWNUM as partition_name,
            COUNT(*) as row_count,
            AVG(price) as avg_price,
            MAX(price) as max_price
        FROM (
            SELECT price FROM products_hash PARTITION(SYS_P81)
            UNION ALL SELECT price FROM products_hash PARTITION(SYS_P82)
            UNION ALL SELECT price FROM products_hash PARTITION(SYS_P83)
            UNION ALL SELECT price FROM products_hash PARTITION(SYS_P84)
        )
        GROUP BY 'SYS_P' || ROWNUM
    ) LOOP
        NULL; -- 只是为了测试执行时间
    END LOOP;
    
    v_end_time := SYSTIMESTAMP;
    v_elapsed_seconds := EXTRACT(SECOND FROM (v_end_time - v_start_time));
    
    DBMS_OUTPUT.PUT_LINE('分区统计执行时间: ' || v_elapsed_seconds || ' 秒');
    
    -- 重置并行度
    EXECUTE IMMEDIATE 'ALTER TABLE products_hash NOPARALLEL';
    
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('测试过程出错: ' || SQLERRM);
        EXECUTE IMMEDIATE 'ALTER TABLE products_hash NOPARALLEL';
END;
/

-- 执行性能测试
EXEC test_hash_partition_performance(2);
EXEC test_hash_partition_performance(4);

5. 复合分区 (Composite Partitioning)

5.1 范围-哈希复合分区

5.1.1 按日期范围和哈希的复合分区

-- 创建范围-哈希复合分区表
CREATE TABLE sales_composite_range_hash (
    sale_id NUMBER,
    customer_id NUMBER,
    product_id NUMBER,
    sale_date DATE,
    amount NUMBER(10,2),
    quantity NUMBER,
    sales_rep_id NUMBER,
    region VARCHAR2(20)
)
PARTITION BY RANGE (sale_date)
SUBPARTITION BY HASH (customer_id)
SUBPARTITIONS 4
(
    PARTITION sales_2022 VALUES LESS THAN (DATE '2023-01-01'),
    PARTITION sales_2023 VALUES LESS THAN (DATE '2024-01-01'),
    PARTITION sales_2024 VALUES LESS THAN (DATE '2025-01-01'),
    PARTITION sales_future VALUES LESS THAN (MAXVALUE)
);

-- 查看复合分区结构
SELECT 
    partition_name,
    subpartition_name,
    high_value,
    subpartition_position
FROM user_tab_subpartitions
WHERE table_name = 'SALES_COMPOSITE_RANGE_HASH'
ORDER BY partition_name, subpartition_position;

-- 插入测试数据
DECLARE
    v_regions SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST('NORTH', 'SOUTH', 'EAST', 'WEST');
BEGIN
    FOR i IN 1..20000 LOOP
        INSERT INTO sales_composite_range_hash VALUES (
            i,
            TRUNC(DBMS_RANDOM.VALUE(1, 1000)),
            TRUNC(DBMS_RANDOM.VALUE(1, 500)),
            DATE '2022-01-01' + DBMS_RANDOM.VALUE(0, 1095), -- 3年范围
            ROUND(DBMS_RANDOM.VALUE(10, 5000), 2),
            TRUNC(DBMS_RANDOM.VALUE(1, 20)),
            TRUNC(DBMS_RANDOM.VALUE(1, 50)),
            v_regions(TRUNC(DBMS_RANDOM.VALUE(1, 5)))
        );
        
        IF MOD(i, 2000) = 0 THEN
            COMMIT;
            DBMS_OUTPUT.PUT_LINE('已插入 ' || i || ' 条记录');
        END IF;
    END LOOP;
    COMMIT;
END;
/

-- 收集统计信息
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'SALES_COMPOSITE_RANGE_HASH');

-- 分析数据分布
SELECT 
    partition_name,
    subpartition_name,
    num_rows,
    blocks
FROM user_tab_subpartitions
WHERE table_name = 'SALES_COMPOSITE_RANGE_HASH'
  AND num_rows > 0
ORDER BY partition_name, subpartition_name;

5.1.2 复合分区的查询优化

-- 创建复合分区查询分析存储过程
CREATE OR REPLACE PROCEDURE analyze_composite_partition_queries
AS
    v_count NUMBER;
    v_amount NUMBER;
BEGIN
    DBMS_OUTPUT.PUT_LINE('=== 复合分区查询分析 ===');
    
    -- 查询1: 分区消除 - 只访问特定日期范围
    DBMS_OUTPUT.PUT_LINE('');
    DBMS_OUTPUT.PUT_LINE('查询1: 日期范围查询(分区消除)');
    
    EXPLAIN PLAN FOR
    SELECT COUNT(*), SUM(amount)
    FROM sales_composite_range_hash
    WHERE sale_date BETWEEN DATE '2023-01-01' AND DATE '2023-12-31';
    
    SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(FORMAT => 'BASIC +PARTITION'));
    
    -- 查询2: 子分区消除 - 特定客户和日期
    DBMS_OUTPUT.PUT_LINE('');
    DBMS_OUTPUT.PUT_LINE('查询2: 客户和日期查询(子分区消除)');
    
    EXPLAIN PLAN FOR
    SELECT *
    FROM sales_composite_range_hash
    WHERE customer_id = 123
      AND sale_date BETWEEN DATE '2023-06-01' AND DATE '2023-06-30';
    
    SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(FORMAT => 'BASIC +PARTITION'));
    
    -- 查询3: 跨分区聚合
    DBMS_OUTPUT.PUT_LINE('');
    DBMS_OUTPUT.PUT_LINE('查询3: 跨分区聚合查询');
    
    SELECT 
        EXTRACT(YEAR FROM sale_date) as sale_year,
        region,
        COUNT(*) as transaction_count,
        SUM(amount) as total_amount,
        AVG(amount) as avg_amount
    FROM sales_composite_range_hash
    WHERE sale_date >= DATE '2022-01-01'
    GROUP BY EXTRACT(YEAR FROM sale_date), region
    ORDER BY sale_year, region;
    
    -- 查询4: 分区级别的并行处理
    DBMS_OUTPUT.PUT_LINE('');
    DBMS_OUTPUT.PUT_LINE('查询4: 分区级别统计');
    
    FOR rec IN (
        SELECT 
            partition_name,
            subpartition_name,
            COUNT(*) as row_count,
            SUM(amount) as total_amount
        FROM (
            SELECT 'SALES_2023' as partition_name, 'SYS_SUBP101' as subpartition_name, amount 
            FROM sales_composite_range_hash SUBPARTITION(SYS_SUBP101)
            UNION ALL
            SELECT 'SALES_2023', 'SYS_SUBP102', amount 
            FROM sales_composite_range_hash SUBPARTITION(SYS_SUBP102)
            UNION ALL
            SELECT 'SALES_2023', 'SYS_SUBP103', amount 
            FROM sales_composite_range_hash SUBPARTITION(SYS_SUBP103)
            UNION ALL
            SELECT 'SALES_2023', 'SYS_SUBP104', amount 
            FROM sales_composite_range_hash SUBPARTITION(SYS_SUBP104)
        )
        GROUP BY partition_name, subpartition_name
        ORDER BY partition_name, subpartition_name
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.partition_name || '.' || rec.subpartition_name || 
                           ': ' || rec.row_count || ' 行, 总额: $' || ROUND(rec.total_amount, 2));
    END LOOP;
    
END;
/

-- 执行复合分区分析
EXEC analyze_composite_partition_queries;

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

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到