在數據庫管理中,序列刷新是一項重要操作。GBase 8c 數據庫推出的增強版序列刷新存儲過程,為用户帶來了更靈活、精確的序列刷新體驗。本文將詳細介紹該存儲過程的使用方法、應用場景及注意事項,助您輕鬆應對各種複雜的序列刷新需求。

01 功能概述

GBase 8c 數據庫的增強版序列刷新存儲過程是一項重要的功能升級。它新增了兩個關鍵參數,分別是 target_table 和 increment_value。

target_table 參數允許用户指定要刷新序列的表名,這使得序列刷新能夠更有針對性地進行,避免了對無關表序列的操作。而 increment_value 參數則讓用户可以自定義序列增加值,默認值為 1。通過這兩個參數的結合使用,用户能夠根據實際需求靈活調整序列刷新的方式,實現更加精確的序列管理。

這兩個新增參數的引入,大大提升了序列刷新的靈活性和精確性。在以往的序列刷新操作中,用户可能只能進行較為寬泛的操作,無法精準地對特定表的序列進行處理。而現在藉助 target_table 參數,用户可以直接指定目標表,確保只對相關表的序列進行刷新。同時,increment_value 參數讓用户可以根據業務需求,靈活設置序列的增加值,滿足不同場景下的序列增長要求。例如,在批量插入數據時,可以預先將序列值增加一定數量,避免插入過程中出現序列衝突。

02 存儲過程定義

GBase 8c 數據庫提供了兩種版本的存儲過程,分別是詳細版和簡化版。

詳細版存儲過程

refresh_all_sequences_enhanced

功能強大,它接受三個參數,分別是 target_schema、target_table 和 increment_value。該存儲過程返回一個表,包含序列名稱、表名、列名、舊值、新值和狀態等信息。通過這些信息,用户可以詳細瞭解每個序列的刷新情況,便於進行監控和管理。

CREATE OR REPLACE FUNCTION bind_sequences_without_creation(
    target_schema TEXT DEFAULT NULL,
    default_column_name TEXT DEFAULT 'id_'
)
RETURNS TABLE(
    table_name TEXT,
    sequence_name TEXT,
    column_name TEXT,
    status TEXT,
    message TEXT
) AS $$
DECLARE
    table_rec RECORD;
    seq_name TEXT;
    sql_stmt TEXT;
    col_exists BOOLEAN;
    seq_exists BOOLEAN;
    is_bound BOOLEAN;
    primary_key_col TEXT;
BEGIN
    -- 遍歷指定模式下的所有基表(排除系統表和視圖)
    FOR table_rec IN 
        SELECT 
            n.nspname AS schema_name,
            c.relname AS table_name
        FROM pg_class c
        JOIN pg_namespace n ON n.oid = c.relnamespace
        WHERE c.relkind = 'r'  -- 基表
          AND (target_schema IS NULL OR n.nspname = target_schema)  -- 模式過濾
          AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'information_schema_priv')  -- 排除系統模式
    LOOP
        BEGIN
            -- 生成序列名(表名_seq)
            seq_name := table_rec.table_name || '_seq';
            
            -- 嘗試查找表的主鍵列
            SELECT a.attname INTO primary_key_col
            FROM pg_index i
            JOIN pg_class c ON c.oid = i.indrelid
            JOIN pg_namespace n ON n.oid = c.relnamespace
            JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(i.indkey)
            WHERE n.nspname = table_rec.schema_name
              AND c.relname = table_rec.table_name
              AND i.indisprimary  -- 主鍵索引
            LIMIT 1;
            
            -- 如果沒有找到主鍵列,則使用默認列名
            IF primary_key_col IS NULL THEN
                primary_key_col := default_column_name;
            END IF;
            
            -- 檢查目標列是否存在
            SELECT EXISTS (
                SELECT 1 
                FROM pg_attribute a
                JOIN pg_class tc ON tc.oid = a.attrelid
                JOIN pg_namespace tn ON tn.oid = tc.relnamespace
                WHERE tc.relname = table_rec.table_name
                  AND tn.nspname = table_rec.schema_name
                  AND a.attname = primary_key_col
                  AND a.attnum > 0
            ) INTO col_exists;
            
            -- 檢查序列是否存在
            SELECT EXISTS (
                SELECT 1
                FROM pg_class sc
                JOIN pg_namespace sn ON sn.oid = sc.relnamespace
                WHERE sc.relname = seq_name
                  AND sn.nspname = table_rec.schema_name
                  AND sc.relkind = 'S'
            ) INTO seq_exists;
            
            -- 如果目標列不存在,記錄並跳過
            IF NOT col_exists THEN
                RETURN QUERY SELECT 
                    table_rec.schema_name || '.' || table_rec.table_name,
                    seq_name,
                    primary_key_col,
                    'SKIPPED'::TEXT,
                    '表中不存在目標列'::TEXT;
                CONTINUE;
            END IF;
            
            -- 如果序列不存在,記錄並跳過(不再自動創建)
            IF NOT seq_exists THEN
                RETURN QUERY SELECT 
                    table_rec.schema_name || '.' || table_rec.table_name,
                    seq_name,
                    primary_key_col,
                    'SKIPPED'::TEXT,
                    '序列不存在'::TEXT;
                CONTINUE;
            END IF;
            
            -- 檢查序列是否已經綁定到該列
            SELECT EXISTS (
                SELECT 1
                FROM pg_attrdef ad
                JOIN pg_class ac ON ac.oid = ad.adrelid
                JOIN pg_namespace an ON an.oid = ac.relnamespace
                JOIN pg_attribute aa ON aa.attrelid = ad.adrelid AND aa.attnum = ad.adnum
                WHERE ac.relname = table_rec.table_name
                  AND an.nspname = table_rec.schema_name
                  AND aa.attname = primary_key_col
                  AND (ad.adbin ILIKE '%' || seq_name || '%' OR ad.adbin ILIKE '%' || table_rec.schema_name || '.' || seq_name || '%')
            ) INTO is_bound;
            
            -- 如果已經綁定,記錄並跳過
            IF is_bound THEN
                RETURN QUERY SELECT 
                    table_rec.schema_name || '.' || table_rec.table_name,
                    seq_name,
                    primary_key_col,
                    'SKIPPED'::TEXT,
                    '序列已綁定到目標列'::TEXT;
                CONTINUE;
            END IF;
            
            -- 綁定序列到目標列(設置默認值)
            sql_stmt := 'ALTER TABLE ' || quote_ident(table_rec.schema_name) || '.' || quote_ident(table_rec.table_name) || 
                       ' ALTER COLUMN ' || quote_ident(primary_key_col) || ' SET DEFAULT nextval(''' || table_rec.schema_name || '.' || seq_name || ''')';
            EXECUTE sql_stmt;
            
            -- 設置序列所有權
            sql_stmt := 'ALTER SEQUENCE ' || quote_ident(table_rec.schema_name) || '.' || quote_ident(seq_name) || 
                       ' OWNED BY ' || quote_ident(table_rec.schema_name) || '.' || quote_ident(table_rec.table_name) || '.' || quote_ident(primary_key_col);
            EXECUTE sql_stmt;
            
            -- 返回成功信息
            RETURN QUERY SELECT 
                table_rec.schema_name || '.' || table_rec.table_name,
                seq_name,
                primary_key_col,
                'SUCCESS'::TEXT,
                '序列已成功綁定到目標列'::TEXT;
                
        EXCEPTION
            WHEN OTHERS THEN
                -- 記錄錯誤信息並繼續處理下一個表
                RETURN QUERY SELECT 
                    table_rec.schema_name || '.' || table_rec.table_name,
                    seq_name,
                    COALESCE(primary_key_col, default_column_name),
                    'ERROR'::TEXT,
                    SQLERRM::TEXT;
        END;
    END LOOP;
    
    RETURN;
END;
$$ LANGUAGE plpgsql;

簡化版存儲過程

refresh_all_sequences_simple_enhanced

則更加簡潔,它同樣接受三個參數,但返回值為 VOID。該存儲過程主要用於簡單地執行序列刷新操作,並輸出相應的日誌信息。對於只需要進行序列刷新而不需要詳細結果信息的用户來説,簡化版存儲過程是一個不錯的選擇。這兩種存儲過程的設計,滿足了不同用户的需求,無論是需要詳細信息的管理員,還是隻關注操作結果的普通用户,都能找到適合自己的存儲過程。

CREATE OR REPLACE FUNCTION bind_sequences_without_creation_simple(
target_schema TEXT DEFAULT NULL,
default_column_name TEXT DEFAULT 'id_'
)
RETURNS VOID AS $$
DECLARE
table_rec RECORD;
seq_name TEXT;
sql_stmt TEXT;
col_exists BOOLEAN;
seq_exists BOOLEAN;
is_bound BOOLEAN;
primary_key_col TEXT;
processed_count INTEGER := 0;
bound_count INTEGER := 0;
skipped_count INTEGER := 0;
error_count INTEGER := 0;
BEGIN
-- 遍歷指定模式下的所有基表(排除系統表和視圖)
FOR table_rec IN
SELECT
n.nspname AS schema_name,
c.relname AS table_name
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r'  -- 基表
AND (target_schema IS NULL OR n.nspname = target_schema)  -- 模式過濾
AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'information_schema_priv')  -- 排除系統模式
LOOP
BEGIN
processed_count := processed_count + 1;

-- 生成序列名(表名_seq)
        seq_name := table_rec.table_name || '_seq';
        
        -- 嘗試查找表的主鍵列
        SELECT a.attname INTO primary_key_col
        FROM pg_index i
        JOIN pg_class c ON c.oid = i.indrelid
        JOIN pg_namespace n ON n.oid = c.relnamespace
        JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(i.indkey)
        WHERE n.nspname = table_rec.schema_name
          AND c.relname = table_rec.table_name
          AND i.indisprimary  -- 主鍵索引
        LIMIT 1;
        
        -- 如果沒有找到主鍵列,則使用默認列名
        IF primary_key_col IS NULL THEN
            primary_key_col := default_column_name;
        END IF;
        
        -- 檢查目標列是否存在
        SELECT EXISTS (
            SELECT 1 
            FROM pg_attribute a
            JOIN pg_class tc ON tc.oid = a.attrelid
            JOIN pg_namespace tn ON tn.oid = tc.relnamespace
            WHERE tc.relname = table_rec.table_name
              AND tn.nspname = table_rec.schema_name
              AND a.attname = primary_key_col
              AND a.attnum > 0
        ) INTO col_exists;
        
        -- 檢查序列是否存在
        SELECT EXISTS (
            SELECT 1
            FROM pg_class sc
            JOIN pg_namespace sn ON sn.oid = sc.relnamespace
            WHERE sc.relname = seq_name
              AND sn.nspname = table_rec.schema_name
              AND sc.relkind = 'S'
        ) INTO seq_exists;
        
        -- 如果目標列不存在,記錄並跳過
        IF NOT col_exists THEN
            skipped_count := skipped_count + 1;
            RAISE NOTICE '跳過表 %.%: 表中不存在目標列 %', table_rec.schema_name, table_rec.table_name, primary_key_col;
            CONTINUE;
        END IF;
        
        -- 如果序列不存在,記錄並跳過(不再自動創建)
        IF NOT seq_exists THEN
            skipped_count := skipped_count + 1;
            RAISE NOTICE '跳過表 %.%: 序列 %.% 不存在', table_rec.schema_name, table_rec.table_name, table_rec.schema_name, seq_name;
            CONTINUE;
        END IF;
        
        -- 檢查序列是否已經綁定到該列
        SELECT EXISTS (
            SELECT 1
            FROM pg_attrdef ad
            JOIN pg_class ac ON ac.oid = ad.adrelid
            JOIN pg_namespace an ON an.oid = ac.relnamespace
            JOIN pg_attribute aa ON aa.attrelid = ad.adrelid AND aa.attnum = ad.adnum
            WHERE ac.relname = table_rec.table_name
              AND an.nspname = table_rec.schema_name
              AND aa.attname = primary_key_col
              AND (ad.adbin ILIKE '%' || seq_name || '%' OR ad.adbin ILIKE '%' || table_rec.schema_name || '.' || seq_name || '%')
        ) INTO is_bound;
        
        -- 如果已經綁定,記錄並跳過
        IF is_bound THEN
            skipped_count := skipped_count + 1;
            RAISE NOTICE '跳過表 %.%: 序列已綁定到目標列 %', table_rec.schema_name, table_rec.table_name, primary_key_col;
            CONTINUE;
        END IF;
        
        -- 綁定序列到目標列(設置默認值)
        sql_stmt := 'ALTER TABLE ' || quote_ident(table_rec.schema_name) || '.' || quote_ident(table_rec.table_name) || 
                   ' ALTER COLUMN ' || quote_ident(primary_key_col) || ' SET DEFAULT nextval(''' || table_rec.schema_name || '.' || seq_name || ''')';
        EXECUTE sql_stmt;
        
        -- 設置序列所有權
        sql_stmt := 'ALTER SEQUENCE ' || quote_ident(table_rec.schema_name) || '.' || quote_ident(seq_name) || 
                   ' OWNED BY ' || quote_ident(table_rec.schema_name) || '.' || quote_ident(table_rec.table_name) || '.' || quote_ident(primary_key_col);
        EXECUTE sql_stmt;
        
        bound_count := bound_count + 1;
        RAISE NOTICE '序列 %.% 已成功綁定到表 %.% 的 % 列', 
                     table_rec.schema_name, seq_name, table_rec.schema_name, table_rec.table_name, primary_key_col;
            
    EXCEPTION
        WHEN OTHERS THEN
            error_count := error_count + 1;
            RAISE WARNING '處理表 %.% 時發生錯誤: %', table_rec.schema_name, table_rec.table_name, SQLERRM;
    END;
END LOOP;

RAISE NOTICE '處理完成: 總計處理 % 個表,綁定 % 個序列,跳過 % 個表,錯誤 % 個', 
             processed_count, bound_count, skipped_count, error_count);
END;

$$
LANGUAGE plpgsql;

$$

03 參數説明

在使用存儲過程時,瞭解參數的含義和作用至關重要。

target_schema  參數用於指定模式名,當該參數為 NULL 時,表示對所有模式進行操作。這為用户提供了靈活的選擇,用户可以根據實際情況,選擇對特定模式或所有模式的序列進行刷新。

target_table  參數用於指定表名,同樣,當該參數為 NULL 時,表示對所有表進行操作。該參數使用模糊匹配,會匹配包含指定表名的所有表,方便用户對相關表的序列進行統一處理。

increment_value  參數是用於指定序列增加值的,默認值為 1。用户可以根據業務需求,自定義該參數的值。例如,在批量插入大量數據時,可以將該參數設置為較大的值,預先為插入操作預留足夠的序列值,避免插入過程中出現序列衝突。在使用這些參數時,需要注意參數的順序,必須按順序傳遞,如果要使用後面的參數,前面的參數不能省略,但可以傳 NULL。

04 詳細使用示例

示例 1: 刷新所有序列(默認行為)
-- 刷新所有序列,增加值為默認值 1
SELECT * FROM refresh_all_sequences_enhanced();

示例 2: 刷新指定模式下的所有序列
-- 刷新 public 模式下的所有序列
SELECT * FROM refresh_all_sequences_enhanced('public');

示例 3: 刷新指定表相關的序列
--刷新所有模式下 users 表相關的序列
SELECT * FROM refresh_all_sequences_enhanced(NULL, 'users');

示例 4: 刷新指定模式下指定表的序列
-- 刷新 public 模式下 users 表相關的序列 SELECT * FROM refresh_all_sequences_enhanced('public', 'users');

示例 5: 自定義序列增加值
-- 刷新 public 模式下 users 表相關的序列,增加值為 10SELECT * FROM refresh_all_sequences_enhanced('public', 'users', 10);

示例 6: 使用簡化版存儲過程刷新所有序列
-- 刷新所有序列,僅輸出日誌
SELECT refresh_all_sequences_simple_enhanced();

示例 7: 使用簡化版存儲過程刷新指定表的序列
-- 刷新 public 模式下 users 表相關的序列 SELECT refresh_all_sequences_simple_enhanced('public', 'users');

示例 8: 使用簡化版存儲過程並自定義增加值
-- 刷新 public 模式下 users 表相關的序列,增加值為 5
SELECT refresh_all_sequences_simple_enhanced('public', 'users', 5);

05 實際應用場景

場景 1: 數據遷移後刷新特定表的序列

--假設我們從外部系統導入了 users 表的數據--現在需要刷新 users 表相關的序列

SELECT refresh_all_sequences_simple_enhanced('public', 'users');

場景 2: 為批量插入預留序列值

--如果計劃批量插入 100 條記錄,可以預先將序列值增加 100--這樣可以避免在批量插入過程中序列衝突

SELECT refresh_all_sequences_simple_enhanced('public', 'orders', 100);

場景 3: 監控特定表的序列狀態

--檢查 users 表相關序列的狀態

SELECT sequence_name, table_name, old_value, new_value, statusFROM refresh_all_sequences_enhanced(NULL, 'users')WHERE status != 'SUCCESS';

場景 4: 在數據導入腳本中使用

--完整的數據導入和序列刷新示例

DO
$$
BEGIN
RAISE NOTICE '開始導入 users 數據...';
-- 執行數據導入(示例)    
-- COPY users FROM '/path/to/users.csv' WITH CSV HEADER;
RAISE NOTICE '數據導入完成,開始刷新序列...';
-- 刷新users表相關序列   
PERFORM refresh_all_sequences_simple_enhanced('public', 'users');
RAISE NOTICE '序列刷新完成';
END

$$;

高級用法-組合使用多個參數

-- 複雜場景:刷新特定模式下特定表的序列,並設置較大的增量值
SELECT refresh_all_sequences_simple_enhanced('sales', 'orders', 1000);
查詢失敗的序列刷新操作-- 查找刷新失敗的序列以便進一步處理
SELECT * FROM refresh_all_sequences_enhanced('public', 'users')WHERE status LIKE 'ERROR%';

高級用法-僅查看將要刷新的序列(不實際執行)

-- 可以先查看將要處理哪些序列,再決定是否執行刷新
SELECT sequence_name, table_name, column_nameFROM refresh_all_sequences_enhanced('public', 'users')WHERE status = 'SUCCESS';

06 注意事項與性能考慮

在使用存儲過程時,有一些注意事項需要用户牢記。首先,參數順序必須嚴格按照定義傳遞,如果要使用後面的參數,前面的參數不能省略,但可以傳 NULL。其次,target_table 參數使用模糊匹配,會匹配包含指定表名的所有表,用户在使用時需要注意這一點,避免誤操作。當不指定參數時,存儲過程的行為與原始版本一致,用户可以根據實際情況選擇是否使用新增參數。此外,存儲過程包含了完善的錯誤處理機制,即使某個序列刷新失敗也不會影響其他序列的處理,但執行這些存儲過程需要有足夠的權限訪問系統表和序列。

在性能方面,也有一些需要考慮的因素。如果數據庫中有大量序列,建議指定模式或表名來減少處理時間,避免對不必要的序列進行操作。在高併發環境下執行序列刷新時,建議在維護窗口期間進行,以減少對業務的影響。同時,建議將序列刷新操作包裝在事務中,以便在出現問題時可以回滾,保證數據的一致性和完整性。通過注意這些事項和考慮性能因素,用户可以更加高效、安全地使用 GBase 8c 數據庫的序列刷新存儲過程。