パーティショニングに関して②

続きです。

Systemパーティショニング

  • Oracle Databaseによって制御されずにアプリケーション側で制御する必要性のあるパーティショニング
  • 例えば、どのパーティションにデータを挿入するか指定しないとエラーになります。

SYSTEMパーティショニングの作成

CREATE TABLE system_partitioned_tab (
  id           NUMBER,
  code         VARCHAR2(10),
  description  VARCHAR2(50),
  created_date DATE
)
PARTITION BY SYSTEM
(
  PARTITION sort_1,
  PARTITION sort_2
);

データの挿入

INSERT INTO system_partitioned_tab PARTITION (sort_1) VALUES (1, 'ONE', 'ONE', SYSDATE);
INSERT INTO system_partitioned_tab PARTITION (sort_2) VALUES (2, 'TWO', 'TOW', SYSDATE);
COMMIT;

EXEC DBMS_STATS.gather_table_stats(USER, 'SYSTEM_PARTITIONED_TAB');

col table_name for a25
col partition_name for a20
col high_value for a10

SELECT table_name, partition_name, high_value, num_rows
FROM user_tab_partitions
ORDER BY table_name, partition_name;
TABLE_NAME                PARTITION_NAME       HIGH_VALUE   NUM_ROWS
------------------------- -------------------- ---------- ----------
SYSTEM_PARTITIONED_TAB    SORT_1                                   1
SYSTEM_PARTITIONED_TAB    SORT_2                                   1

参照パーティション

参照パーティションの作成

CREATE TABLE parent_tab (
  id           NUMBER       NOT NULL,
  code         VARCHAR2(10) NOT NULL,
  description  VARCHAR2(50),
  created_date DATE,
  CONSTRAINT parent_tab_pk PRIMARY KEY (id)
)
PARTITION BY RANGE (created_date)
(
  PARTITION part_2017 VALUES LESS THAN (TO_DATE('01-JAN-2018', 'DD-MON-YYYY')),
  PARTITION part_2018 VALUES LESS THAN (TO_DATE('01-JAN-2019', 'DD-MON-YYYY'))
);

CREATE TABLE child_tab (
  id            NUMBER NOT NULL,
  parent_tab_id NUMBER NOT NULL,
  code          VARCHAR2(10),
  description   VARCHAR2(50),
  created_date  DATE,
  CONSTRAINT child_parent_tab_pk PRIMARY KEY (id),
  CONSTRAINT child_parent_tab_fk FOREIGN KEY (parent_tab_id) REFERENCES parent_tab (id)
) 
PARTITION BY REFERENCE (child_parent_tab_fk);

データの挿入

INSERT INTO parent_tab VALUES (1, 'ONE', '1 ONE', SYSDATE);
INSERT INTO parent_tab VALUES (2, 'TWO', '2 TWO', SYSDATE);
INSERT INTO parent_tab VALUES (3, 'THREE', '3 THREE', ADD_MONTHS(SYSDATE, 12));

INSERT INTO child_tab VALUES (1, 1, 'ONE', '1 1 ONE', SYSDATE);
INSERT INTO child_tab VALUES (2, 3, 'TWO', '2 3 TWO', SYSDATE);
INSERT INTO child_tab VALUES (3, 3, 'THREE', '3 3 THREE', SYSDATE);

COMMIT;
EXEC DBMS_STATS.gather_table_stats(USER, 'PARENT_TAB');
EXEC DBMS_STATS.gather_table_stats(USER, 'CHILD_TAB');

データの確認

col table_name for a20
col partition_name for a20
col tablespace_name for a20

SELECT table_name, partition_name, high_value, num_rows
FROM user_tab_partitions;
TABLE_NAME           PARTITION_NAME       HIGH_VALUE                       NUM_ROWS
-------------------- -------------------- ------------------------------ ----------
CHILD_TAB            PART_2017                                                    1
CHILD_TAB            PART_2018                                                    2
PARENT_TAB           PART_2017            TO_DATE(' 2018-01-01 00:00:00'          2
                                          , 'SYYYY-MM-DD HH24:MI:SS', 'N
                                          LS_CALENDAR=GREGORIAN')

PARENT_TAB           PART_2018            TO_DATE(' 2019-01-01 00:00:00'          1
                                          , 'SYYYY-MM-DD HH24:MI:SS', 'N
                                          LS_CALENDAR=GREGORIAN')

仮想列パーティション

仮想列パーティション表の作成

  • NLS_LENGTH_SEMANTICSをCHARに設定する必要性があります。
alter session set nls_length_semantics='CHAR';
CREATE TABLE users (
  id           NUMBER,
  username     VARCHAR2(20),
  first_letter VARCHAR2(1)
    GENERATED ALWAYS AS
      (
        UPPER(SUBSTR(TRIM(username), 1, 1))
      ) VIRTUAL
)
PARTITION BY LIST (first_letter)
(
  PARTITION part_a_g VALUES ('A','B','C','D','E','F','G'),
  PARTITION part_h_n VALUES ('H','I','J','K','L','M','N'),
  PARTITION part_o_u VALUES ('O','P','Q','R','S','T','U'),
  PARTITION part_v_z VALUES ('V','W','X','Y','Z')
);

データの挿入

INSERT INTO users (id, username) VALUES (1, 'Andy Pandy');
INSERT INTO users (id, username) VALUES (1, 'Burty Basset');
INSERT INTO users (id, username) VALUES (1, 'Harry, Hill');
INSERT INTO users (id, username) VALUES (1, 'Iggy Pop');
INSERT INTO users (id, username) VALUES (1, 'Oliber Hardy');
INSERT INTO users (id, username) VALUES (1, 'Peter Pervis');
INSERT INTO users (id, username) VALUES (1, 'Veruca Salt');
INSERT INTO users (id, username) VALUES (1, 'Wiley Cyote');
COMMIT;

EXEC DBMS_STATS.gather_table_stats(USER, 'USERS');

データの確認

col table_name for a25
col partition_name for a20
col high_value for a40

SELECT table_name, partition_name, high_value, num_rows
FROM  user_tab_partitions
ORDER BY table_name, partition_name;
TABLE_NAME                PARTITION_NAME       HIGH_VALUE                                 NUM_ROWS
------------------------- -------------------- ---------------------------------------- ----------
USERS                     PART_A_G             'A', 'B', 'C', 'D', 'E', 'F', 'G'                 2
USERS                     PART_H_N             'H', 'I', 'J', 'K', 'L', 'M', 'N'                 2
USERS                     PART_O_U             'O', 'P', 'Q', 'R', 'S', 'T', 'U'                 2
USERS                     PART_V_Z             'V', 'W', 'X', 'Y', 'Z'                           2

パーティショニングに関して

Oracle 12cR2がCloudオンリーだけど正式にリリースされて、シャーディングがサポートがされました。今までShared Everythingという基本的な考え方を崩さず様々な機能追加を行ってきたOracleとしては思いっ切った舵をきった感じがします。やはりCloud対応を推し進めていく中でスケールアウト・スケールバックのしやすさをDatabaseにも、ということなのでしょうか。 個人的には依存するコンポーネントが増え、実案件で運用していくにはかなり厳しい印象ですが、、、いくつかの人柱は必要でしょう。

さて、シャーディングは物理的に分散したDBサーバ上で水平分割を行う技術ですが、その元になっている単一DBサーバ上での水平分割であるパーティショニングについてメモ書き程度に残しておきます。

コンポジットパーティショニング

Oracle Database 12cR1までのリリースではサポートしているパーティショニングの方式は以下になります。

  • Range
  • List
  • Hash
  • Refresh

これらの方式を組み合わせたパーティショニングをコンポジットパーティショニングといいます。(*組み合わせによっては実現できないものもあります。)

List-Hash コンポジットパーティショニング

  • パーティショニング表の作成
CREATE TABLE list_hash_tab (
  id           NUMBER,
  code         VARCHAR2(10),
  description  VARCHAR2(50),
  created_date DATE
)
PARTITION BY LIST (code)
SUBPARTITION BY HASH (id)
(
  PARTITION part_aa VALUES ('AA')
  (
    SUBPARTITION part_aa_01,
    SUBPARTITION part_aa_02
  ),
  PARTITION part_bb VALUES ('BB')
  (
    SUBPARTITION part_bb_01,
    SUBPARTITION part_bb_02
  )
);
  • 作成したList-Hashコンポジットパーティショニング表にデータを挿入 + 統計情報の収集
DECLARE
  l_code VARCHAR2(10);
BEGIN
  FOR i IN 1 .. 40 LOOP
    IF MOD(i, 2) = 0 THEN
      l_code := 'BB';
    ELSE
      l_code := 'AA';
    END IF;
    
    INSERT INTO list_hash_tab (id, code, description, created_date)
    VALUES (i, l_code, 'Description for ' || i || ' ' || l_code, SYSDATE);
  END LOOP;
  COMMIT;
END;
/

-- granularityパラメータは統計情報の収集の粒度を決めるもの(パーティション表の場合のみ)
-- ALLの場合、サブパーティション、パーティション、グローバル全て収集する
EXEC DBMS_STATS.gather_table_stats(USER, 'LIST_HASH_TAB', granularity=>'ALL');

col table_name for a20
col partition_name for a20
col subpartition_name for a20

SELECT table_name, partition_name, subpartition_name, num_rows
FROM user_tab_subpartitions
ORDER BY table_name, partition_name, subpartition_name;
TABLE_NAME           PARTITION_NAME       SUBPARTITION_NAME      NUM_ROWS
-------------------- -------------------- -------------------- ----------
LIST_HASH_TAB        PART_AA              PART_AA_01                    7
LIST_HASH_TAB        PART_AA              PART_AA_02                   13
LIST_HASH_TAB        PART_BB              PART_BB_01                   10
LIST_HASH_TAB        PART_BB              PART_BB_02                   10

INTERVALパーティション

CREATE TABLE interval_tab (
  id NUMBER,
  code VARCHAR2(10),
  description VARCHAR2(50),
  created_date DATE
)
PARTITION BY RANGE (created_date)
INTERVAL (NUMTOYMINTERVAL(1, 'MONTH'))
(
  PARTITION part_01 VALUES LESS THAN (TO_DATE('01-NOV-2007', 'DD-MON-YYYY'))
);
EXEC DBMS_STATS.gather_table_stats(USER, 'INTERVAL_TAB');

col table_name for a20
col partition_name for a20
col high_value for a40

SELECT table_name, partition_name, high_value, num_rows
FROM user_tab_partitions
ORDER BY table_name, partition_name;
INSERT INTO interval_tab VALUES (1, 'ONE', 'DESC 1', TO_DATE('10-OCT-2007', 'DD-MON-YYYY'));
INSERT INTO interval_tab VALUES (2, 'TWO', 'DESC 2', TO_DATE('31-OCT-2007', 'DD-MON-YYYY'));
COMMIT;
EXEC DBMS_STATS.gather_table_stats(USER, 'INTERVAL_TAB');

SELECT table_name, partition_name, high_value, num_rows
FROM user_tab_partitions
ORDER BY table_name, partition_name;
TABLE_NAME           PARTITION_NAME       HIGH_VALUE                                 NUM_ROWS
-------------------- -------------------- ---------------------------------------- ----------
INTERVAL_TAB         PART_01              TO_DATE(' 2007-11-01 00:00:00', 'SYYYY-M          2
                                          M-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
                                          N')
INSERT INTO interval_tab VALUES (3, 'THREE', 'DESC 3', TO_DATE('1-NOV-2007', 'DD-MON-YYYY'));
INSERT INTO interval_tab VALUES (4, 'FOUR', 'DESC 4', TO_DATE('10-NOV-2007', 'DD-MON-YYYY'));
COMMIT;

EXEC DBMS_STATS.gather_table_stats(USER, 'INTERVAL_TAB');

SELECT table_name, partition_name, high_value, num_rows
FROM user_tab_partitions
ORDER BY table_name, partition_name;
TABLE_NAME           PARTITION_NAME       HIGH_VALUE                                 NUM_ROWS
-------------------- -------------------- ---------------------------------------- ----------
INTERVAL_TAB         PART_01              TO_DATE(' 2007-11-01 00:00:00', 'SYYYY-M          2
                                          M-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
                                          N')

INTERVAL_TAB         SYS_P526             TO_DATE(' 2007-12-01 00:00:00', 'SYYYY-M          2
                                          M-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
                                          N')
INSERT INTO interval_tab VALUES (5, 'FIVE', 'DEC 5', TO_DATE('01-JAN-2008', 'DD-MON-YYYY'));
INSERT INTO interval_tab VALUES (4, 'FOUR', 'DEC 4', TO_DATE('20-JAN-2008', 'DD-MON-YYYY'));
COMMIT;

EXEC DBMS_STATS.gather_table_stats(USER, 'INTERVAL_TAB');

SELECT table_name, partition_name, high_value, num_rows
FROM user_tab_partitions
ORDER BY table_name, partition_name;
TABLE_NAME           PARTITION_NAME       HIGH_VALUE                                 NUM_ROWS
-------------------- -------------------- ---------------------------------------- ----------
INTERVAL_TAB         PART_01              TO_DATE(' 2007-11-01 00:00:00', 'SYYYY-M          2
                                          M-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
                                          N')

INTERVAL_TAB         SYS_P526             TO_DATE(' 2007-12-01 00:00:00', 'SYYYY-M          2
                                          M-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
                                          N')

INTERVAL_TAB         SYS_P527             TO_DATE(' 2008-02-01 00:00:00', 'SYYYY-M          2
                                          M-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
                                          N')
  • データの挿入。期間が空いた( 2007-12-01 00:00:00 - 2008-01-01 00:00:00)レンジのデータを挿入した場合。新しくパーティションが作成されます。
INSERT INTO interval_tab VALUES (6, 'SIX', 'DESC 6', TO_DATE('04-DEC-2007', 'DD-MON-YYYY'));
INSERT INTO interval_tab VALUES (7, 'SEVEN', 'DESC 7', TO_DATE('30-DEC-2007', 'DD-MON-YYYY'));
COMMIT;

EXEC DBMS_STATS.gather_table_stats(USER, 'INTERVAL_TAB');

SELECT table_name, partition_name, high_value, num_rows
FROM user_tab_partitions
ORDER BY table_name, partition_name;
TABLE_NAME           PARTITION_NAME       HIGH_VALUE                                 NUM_ROWS
-------------------- -------------------- ---------------------------------------- ----------
INTERVAL_TAB         PART_01              TO_DATE(' 2007-11-01 00:00:00', 'SYYYY-M          2
                                          M-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
                                          N')

INTERVAL_TAB         SYS_P526             TO_DATE(' 2007-12-01 00:00:00', 'SYYYY-M          2
                                          M-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
                                          N')

INTERVAL_TAB         SYS_P527             TO_DATE(' 2008-02-01 00:00:00', 'SYYYY-M          2
                                          M-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
                                          N')

INTERVAL_TAB         SYS_P528             TO_DATE(' 2008-01-01 00:00:00', 'SYYYY-M          2
                                          M-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
                                          N')