前言

听说金仓 V9R1C10 兼容了很多 Oracle 的特性,正好最近在做一些分区表的数据归档,测试一下金仓的 Oracle 兼容模式的分区表功能。

范围分区表

创建一个范围分区表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
test=# show database_mode;   
database_mode
---------------
oracle

test=# CREATE TABLE sales_range (
sale_id NUMBER,
product_name VARCHAR2(100),
sale_date DATE,
amount NUMBER(10,2),
region VARCHAR2(50)
)
PARTITION BY RANGE (sale_date) (
PARTITION p_2023_q1 VALUES LESS THAN (TO_DATE('2023-04-01', 'YYYY-MM-DD')),
PARTITION p_2023_q2 VALUES LESS THAN (TO_DATE('2023-07-01', 'YYYY-MM-DD')),
PARTITION p_2023_q3 VALUES LESS THAN (TO_DATE('2023-10-01', 'YYYY-MM-DD')),
PARTITION p_2023_q4 VALUES LESS THAN (TO_DATE('2024-01-01', 'YYYY-MM-DD')),
PARTITION p_2024 VALUES LESS THAN (MAXVALUE)
);
CREATE TABLE

插入测试数据到不同分区:

1
2
3
4
5
6
7
8
9
10
test=# INSERT INTO sales_range VALUES (1, 'Laptop', TO_DATE('2023-02-15', 'YYYY-MM-DD'), 1200.50, 'North');
INSERT 0 1
test=# INSERT INTO sales_range VALUES (2, 'Mouse', TO_DATE('2023-05-20', 'YYYY-MM-DD'), 25.99, 'South');
INSERT 0 1
test=# INSERT INTO sales_range VALUES (3, 'Keyboard', TO_DATE('2023-08-10', 'YYYY-MM-DD'), 80.00, 'East');
INSERT 0 1
test=# INSERT INTO sales_range VALUES (4, 'Monitor', TO_DATE('2023-11-05', 'YYYY-MM-DD'), 350.00, 'West');
INSERT 0 1
test=# INSERT INTO sales_range VALUES (5, 'Tablet', TO_DATE('2024-03-15', 'YYYY-MM-DD'), 499.99, 'North');
INSERT 0 1

验证分区数据分布:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
test=# SELECT 'p_2023_q1' AS partition_name, COUNT(*) FROM sales_range PARTITION (p_2023_q1)
UNION ALL
SELECT 'p_2023_q2', COUNT(*) FROM sales_range PARTITION (p_2023_q2)
UNION ALL
SELECT 'p_2023_q3', COUNT(*) FROM sales_range PARTITION (p_2023_q3)
UNION ALL
SELECT 'p_2023_q4', COUNT(*) FROM sales_range PARTITION (p_2023_q4)
UNION ALL
SELECT 'p_2024', COUNT(*) FROM sales_range PARTITION (p_2024);

partition_name | count
----------------+-------
p_2023_q1 | 1
p_2023_q2 | 1
p_2023_q3 | 1
p_2023_q4 | 1
p_2024 | 1

查看分区数据:

1
2
3
4
5
EXPLAIN SELECT * FROM sales_range WHERE sale_date BETWEEN TO_DATE('2023-05-01', 'YYYY-MM-DD')
AND TO_DATE('2023-06-30', 'YYYY-MM-DD');

SELECT * FROM sales_range WHERE sale_date BETWEEN TO_DATE('2023-05-01', 'YYYY-MM-DD')
AND TO_DATE('2023-06-30', 'YYYY-MM-DD');

LIST 分区

创建一个 LIST 分区表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
test=# CREATE TABLE sales_list (
sale_id NUMBER,
product_name VARCHAR2(100),
amount NUMBER(10,2),
region VARCHAR2(50)
)
PARTITION BY LIST (region) (
PARTITION p_north VALUES ('North', 'Northeast', 'Northwest'),
PARTITION p_south VALUES ('South', 'Southeast', 'Southwest'),
PARTITION p_east VALUES ('East'),
PARTITION p_west VALUES ('West'),
PARTITION p_default VALUES (DEFAULT)
);
CREATE TABLE

插入数据:

1
2
3
4
5
6
7
8
9
10
11
12
test=# INSERT INTO sales_list VALUES (1, 'Product A', 100.00, 'North');
INSERT 0 1
test=# INSERT INTO sales_list VALUES (2, 'Product B', 200.00, 'South');
INSERT 0 1
test=# INSERT INTO sales_list VALUES (3, 'Product C', 150.00, 'East');
INSERT 0 1
test=# INSERT INTO sales_list VALUES (4, 'Product D', 180.00, 'West');
INSERT 0 1
test=# INSERT INTO sales_list VALUES (5, 'Product E', 220.00, 'Central');
INSERT 0 1
test=# INSERT INTO sales_list VALUES (6, 'Product F', 90.00, 'Northeast');
INSERT 0 1

验证分区数据:

1
2
3
4
5
6
7
8
9
10
test=# SELECT * FROM sales_list PARTITION (p_north);
sale_id | product_name | amount | region
---------+--------------+--------+-----------
1 | Product A | 100.00 | North
6 | Product F | 90.00 | Northeast

test=# SELECT * FROM sales_list PARTITION (p_default);
sale_id | product_name | amount | region
---------+--------------+--------+---------
5 | Product E | 220.00 | Central

HASH 分区

创建一个哈希分区:

1
2
3
4
5
6
7
8
9
10
11
12
13
test=# CREATE TABLE customer_hash (
customer_id NUMBER PRIMARY KEY,
customer_name VARCHAR2(100),
email VARCHAR2(100),
phone VARCHAR2(20)
)
PARTITION BY HASH (customer_id) (
PARTITION p1,
PARTITION p2,
PARTITION p3,
PARTITION p4
);
CREATE TABLE

插入数据(会自动根据哈希算法分配到不同分区):

1
2
3
4
5
6
7
8
test=# INSERT INTO customer_hash
SELECT
i,
'Customer_' || i,
'customer' || i || '@example.com',
'1234567' || LPAD(i::TEXT, 3, '0')
FROM generate_series(1, 20) AS i;
INSERT 0 20

查看各分区数据分布:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
test=# SELECT 'p1' AS partition_name, COUNT(*) FROM customer_hash PARTITION (p1)
UNION ALL
SELECT 'p2', COUNT(*) FROM customer_hash PARTITION (p2)
UNION ALL
SELECT 'p3', COUNT(*) FROM customer_hash PARTITION (p3)
UNION ALL
SELECT 'p4', COUNT(*) FROM customer_hash PARTITION (p4);

partition_name | count
----------------+-------
p1 | 5
p2 | 6
p3 | 2
p4 | 7

验证分区数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
test=# SELECT * FROM customer_hash PARTITION (p1);
customer_id | customer_name | email | phone
-------------+---------------+------------------------+------------
4 | Customer_4 | customer4@example.com | 1234567004
7 | Customer_7 | customer7@example.com | 1234567007
10 | Customer_10 | customer10@example.com | 1234567010
12 | Customer_12 | customer12@example.com | 1234567012
16 | Customer_16 | customer16@example.com | 1234567016

test=# SELECT * FROM customer_hash PARTITION (p4);
customer_id | customer_name | email | phone
-------------+---------------+------------------------+------------
2 | Customer_2 | customer2@example.com | 1234567002
6 | Customer_6 | customer6@example.com | 1234567006
11 | Customer_11 | customer11@example.com | 1234567011
13 | Customer_13 | customer13@example.com | 1234567013
17 | Customer_17 | customer17@example.com | 1234567017
18 | Customer_18 | customer18@example.com | 1234567018
19 | Customer_19 | customer19@example.com | 1234567019

间隔分区(INTERVAL 分区)

创建一个间隔分区:

1
2
3
4
5
6
7
8
9
10
11
12
13
-- 每月自动创建新分区
test=# CREATE TABLE logs_interval (
log_id NUMBER,
log_date DATE,
log_level VARCHAR2(20),
message VARCHAR2(500)
)
PARTITION BY RANGE (log_date)
INTERVAL (NUMTOYMINTERVAL(1, 'MONTH'))
(
PARTITION p_start VALUES LESS THAN (TO_DATE('2024-01-01', 'YYYY-MM-DD'))
);
CREATE TABLE

插入不同月份的数据,观察自动分区创建:

1
2
3
4
5
6
7
8
9
10
11
12
test=# INSERT INTO logs_interval VALUES (1, TO_DATE('2023-12-20', 'YYYY-MM-DD'), 'INFO', 'Old log entry');
INSERT 0 1
test=# INSERT INTO logs_interval VALUES (2, TO_DATE('2024-01-15', 'YYYY-MM-DD'), 'ERROR', 'Jan 2024 log');
INSERT 0 1
test=# INSERT INTO logs_interval VALUES (3, TO_DATE('2024-02-10', 'YYYY-MM-DD'), 'WARN', 'Feb 2024 log');
INSERT 0 1
test=# INSERT INTO logs_interval VALUES (4, TO_DATE('2024-03-05', 'YYYY-MM-DD'), 'INFO', 'Mar 2024 log');
INSERT 0 1
test=# INSERT INTO logs_interval VALUES (5, TO_DATE('2024-06-20', 'YYYY-MM-DD'), 'DEBUG', 'Jun 2024 log');
INSERT 0 1
test=# INSERT INTO logs_interval VALUES (6, TO_DATE('2024-10-15', 'YYYY-MM-DD'), 'ERROR', 'Oct 2024 log');
INSERT 0 1

查询所有数据(应该能看到自动创建的分区):

1
2
3
4
5
6
7
8
9
test=# SELECT * FROM logs_interval ORDER BY log_date;
log_id | log_date | log_level | message
--------+---------------------+-----------+---------------
1 | 2023-12-20 00:00:00 | INFO | Old log entry
2 | 2024-01-15 00:00:00 | ERROR | Jan 2024 log
3 | 2024-02-10 00:00:00 | WARN | Feb 2024 log
4 | 2024-03-05 00:00:00 | INFO | Mar 2024 log
5 | 2024-06-20 00:00:00 | DEBUG | Jun 2024 log
6 | 2024-10-15 00:00:00 | ERROR | Oct 2024 log

查看分区定义:

1
2
3
4
5
6
7
8
9
test=# SELECT partition_name, high_value FROM user_tab_partitions WHERE table_name = 'LOGS_INTERVAL';
partition_name | high_value
----------------+-------------------------
P_START | ('2024-01-01 00:00:00')
P1 | ('2024-02-01 00:00:00')
P2 | ('2024-03-01 00:00:00')
P3 | ('2024-04-01 00:00:00')
P4 | ('2024-07-01 00:00:00')
P5 | ('2024-11-01 00:00:00')

分区表 DML 操作

在特定分区插入数据:

1
2
3
test=# INSERT INTO sales_range PARTITION (p_2023_q3)
VALUES (6, 'Headset', TO_DATE('2023-09-15', 'YYYY-MM-DD'), 120.00, 'Central');
INSERT 0 1

更新特定分区数据:

1
2
3
test=# UPDATE sales_range PARTITION (p_2023_q1)
SET amount = amount * 1.1
WHERE region = 'North';

删除特定分区数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
test=# select * FROM sales_range PARTITION (p_2024);
sale_id | product_name | sale_date | amount | region
---------+--------------+---------------------+--------+--------
5 | Tablet | 2024-03-15 00:00:00 | 499.99 | North
(1 行记录)

test=# DELETE FROM sales_range PARTITION (p_2024) WHERE amount < 100;
DELETE 0
test=# DELETE FROM sales_range PARTITION (p_2024) WHERE amount > 100;
DELETE 1
test=# select * FROM sales_range PARTITION (p_2024);
sale_id | product_name | sale_date | amount | region
---------+--------------+-----------+--------+--------
(0 行记录)

TRUNCATE 特定分区:

1
2
3
4
5
6
7
8
9
10
11
12
13
test=# select * FROM sales_range PARTITION (P_2023_q3);
sale_id | product_name | sale_date | amount | region
---------+--------------+---------------------+--------+---------
3 | Keyboard | 2023-08-10 00:00:00 | 80.00 | East
6 | Headset | 2023-09-15 00:00:00 | 120.00 | Central
(2 行记录)

test=# ALTER TABLE sales_range TRUNCATE PARTITION p_2023_q3;
ALTER TABLE
test=# select * FROM sales_range PARTITION (P_2023_q3);
sale_id | product_name | sale_date | amount | region
---------+--------------+-----------+--------+--------
(0 行记录)

分区表 DDL 操作

添加新分区:

1
2
3
4
5
6
7
8
9
10
11
-- 删除 MAXVALUE 分区
test=# ALTER TABLE sales_range DETACH PARTITION sales_range_p_2024;
ALTER TABLE

-- 添加 2025 年分区
test=# ALTER TABLE sales_range ADD PARTITION p_2025 VALUES LESS THAN (TO_DATE('2026-01-01', 'YYYY-MM-DD'));
ALTER TABLE

-- 重新附加或创建 MAXVALUE 分区
test=# CREATE TABLE sales_range_p_future PARTITION OF sales_range FOR VALUES FROM ('2026-01-01') TO (MAXVALUE);
CREATE TABLE

删除分区:

1
2
test=# ALTER TABLE sales_range DROP PARTITION p_2025;
ALTER TABLE

拆分分区:

1
2
3
4
test=# ALTER TABLE sales_range SPLIT PARTITION p_2023_q1
AT ('2023-02-01')
INTO (PARTITION p_2023_h1, PARTITION p_2023_h2);
ALTER TABLE

合并分区:

1
2
test=# ALTER TABLE sales_range MERGE PARTITIONS p_2023_h1, p_2023_h2
INTO PARTITION p_2023_q1;

重命名分区:

1
ALTER TABLE sales_range RENAME PARTITION p_2024_all TO p_2024;

交换分区:

1
2
3
4
5
6
7
8
9
CREATE TABLE temp_sales (
sale_id NUMBER,
product_name VARCHAR2(100),
sale_date DATE,
amount NUMBER(10,2),
region VARCHAR2(50)
);

INSERT INTO temp_sales VALUES (100, 'Test Product', TO_DATE('2023-03-10', 'YYYY-MM-DD'), 999.99, 'Test');

将普通表数据交换到分区:

1
2
3
4
5
6
7
ALTER TABLE sales_range EXCHANGE PARTITION p_2023_q1 WITH TABLE temp_sales;

SELECT * FROM sales_range PARTITION (p_2023_q1);
SELECT * FROM temp_sales;

-- 再交换回来
ALTER TABLE sales_range EXCHANGE PARTITION p_2023_q1 WITH TABLE temp_sales;

写在最后

关于分区表常用的一些分区类型以及功能特性,均已在金仓 V9R1C10 版本的 Oracle 兼容模式下测试,并且全部支持,点赞👍🏻!