未记录事实核验 · 状态:维护中
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用范围与待维护事项
本文适用于具备Greenplum集群运维、SQL与Python能力的工程人员审阅数据流转方案,不是可直接执行的生产脚本。原稿未指定Greenplum/PXF版本,gpextract来源及参数、COPY选项、UPSERT和触发器支持需逐项核实;同步示例缺少last_modified及相应唯一约束,外部表协议与错误日志对象也需配置。示例数据文件、源表和加密扩展未提供;TRUNCATE会清空目标表,增量水位可能漏数,加密示例不等于传输加密。应先在隔离环境补齐依赖并验证,会员标记不代表已通过付费质量验收。
📥 数据导入导出 - 掌握Greenplum数据流转的专业技能
tip 💝 给数据流转新手的温暖指导
🤗 学习Greenplum数据导入导出就像掌握物流管理技巧!
如果你现在面对数据迁移时想:
- "这么大的数据量,要怎么快速导入到Greenplum?"
- "如何保证数据传输过程中的完整性和安全性?"
- "有什么高效的方法来处理不同格式的数据?"
别担心! 每个数据工程师都是从第一个数据文件导入开始的 ✨
🚛 想象数据导入导出就像专业的物流配送系统
物流配送的完整流程:
- 📦 货物准备 = 数据格式化(确保数据符合要求)
- 🚛 运输工具 = 导入方法选择(选择最佳传输方式)
- 📍 路线规划 = 分布策略(优化数据分布)
- 🔍 质量检查 = 数据验证(确保数据完整性)
- 📋 签收确认 = 导入验证(确认导入成功)
熟练掌握数据流转,你就成为了数据世界的物流专家! 🌟
🎯 数据导入导出概览
📊 Greenplum数据流转生态
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
A[外部数据源]
B[数据导入方法]
A --> B --> C[Greenplum数据仓库]
C --> D[数据导出方法]
D --> E[目标系统]
E --> A1[CSV文件<br/>结构化文本]
A --> A2[JSON数据<br/>半结构化]
A2 --> A3[外部数据库<br/>关系型数据]
A --> A4[云存储<br/>S3/HDFS]
A4 --> B1[COPY命令<br/>高性能批量导入]
B --> B2[INSERT语句<br/>事务性导入]
B2 --> B3[gpload工具<br/>ETL流程导入]
B --> B4[外部表<br/>实时访问]
B4 --> D1[COPY命令<br/>批量导出]
D --> D2[SELECT INTO<br/>创建查询结果表,非文件导出]
D2 --> D3[gpextract工具<br/>并行导出]
D --> D4[外部表<br/>实时导出]
D4 --> E1[文件系统<br/>本地/网络存储]
E --> E2[其他数据库<br/>数据同步]
E2 --> E3[分析工具<br/>BI/ML平台]
E --> E4[云平台<br/>数据湖]
style A fill:#e1f5fe
style B fill:#f3e5f5
style C fill:#fff3e0
style D fill:#e8f5e8
style E fill:#fce4ec
🌟 导入导出方法对比
| 方法类型 | 性能表现 | 适用场景 | 复杂度 | 推荐指数 |
|---|---|---|---|---|
| COPY命令 | ⭐⭐⭐⭐⭐ | 大批量数据导入 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| INSERT语句 | ⭐⭐ | 小量数据事务导入 | ⭐ | ⭐⭐⭐ |
| gpload工具 | ⭐⭐⭐⭐ | ETL流程自动化 | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 外部表 | ⭐⭐⭐ | 实时数据访问 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 数据库链接 | ⭐⭐⭐ | 跨数据库同步 | ⭐⭐⭐⭐ | ⭐⭐⭐ |
📥 数据导入方法详解
⚡ COPY命令:高性能批量导入
🚀 基础语法与用法
COPY命令的基本语法:
-- 🏗️ COPY命令基础语法
-- 1. 从文件导入数据
COPY table_name
FROM 'file_path'
WITH (
FORMAT 'csv',
DELIMITER ',',
HEADER true,
QUOTE '"',
ESCAPE '\',
NULL 'NULL'
);
-- 2. 从标准输入导入
COPY table_name
FROM STDIN
WITH (FORMAT 'csv', DELIMITER ',');
-- 3. 从程序输出导入
COPY table_name
FROM PROGRAM 'command'
WITH (FORMAT 'csv');
📊 实际导入案例演示
案例1:导入销售数据CSV文件
-- 🏗️ 创建销售数据表
CREATE TABLE sales_data (
order_id INT,
customer_id INT,
product_name VARCHAR(100),
quantity INT,
unit_price DECIMAL(10,2),
order_date DATE,
sales_amount DECIMAL(12,2)
) DISTRIBUTED BY (customer_id);
-- 📥 导入CSV数据文件
COPY sales_data
FROM '/data/sales/sales_2024.csv'
WITH (
FORMAT 'csv',
DELIMITER ',',
HEADER true, -- 第一行是标题
QUOTE '"', -- 字符串引号
NULL 'NULL', -- 空值表示
FORCE_NULL (product_name), -- 将该列中带引号且匹配NULL标记的值也转换为NULL;需确认版本支持
ENCODING 'UTF8' -- 文件编码
);
-- ✅ 验证导入结果
SELECT
COUNT(*) as total_records,
MIN(order_date) as earliest_date,
MAX(order_date) as latest_date,
SUM(sales_amount) as total_sales
FROM sales_data;
案例2:导入包含JSON字段的制表符分隔数据(文件须按user_id、session_id、event_data、created_at四列组织,event_data列为合法JSON;不是直接导入任意JSON文件)
-- 🏗️ 创建用户行为数据表
CREATE TABLE user_behavior (
user_id INT,
session_id VARCHAR(50),
event_data JSONB,
created_at TIMESTAMP
) DISTRIBUTED BY (user_id);
-- 📥 导入JSON数据
COPY user_behavior (user_id, session_id, event_data, created_at)
FROM '/data/behavior/user_events.json'
WITH (
FORMAT 'csv',
DELIMITER E'\t',
QUOTE E'\b', -- 避免JSON中的引号冲突
ESCAPE E'\b'
);
-- 🔍 查询JSON数据示例
SELECT
user_id,
event_data->>'action' as action_type,
event_data->>'page' as page_url,
(event_data->>'duration')::int as duration_seconds
FROM user_behavior
WHERE event_data->>'action' = 'page_view'
LIMIT 10;
🎯 高级导入选项
性能优化配置:
-- 🚀 高性能导入配置
COPY large_table
FROM '/data/big_file.csv'
WITH (
FORMAT 'csv',
DELIMITER ',',
HEADER true,
FREEZE true, -- 请求冻结装载元组,并非跳过WAL;需确认目标版本及使用条件
OIDS false, -- 不导入OID
ENCODING 'UTF8'
);
-- ⚡ 循环串行导入多个文件
DO $$
DECLARE
file_path TEXT;
BEGIN
FOR file_path IN
SELECT '/data/split_files/part_' || generate_series(1,10) || '.csv'
LOOP
EXECUTE format('
COPY target_table
FROM %L
WITH (FORMAT ''csv'', HEADER true)',
file_path
);
END LOOP;
END $$;
🔄 gpload工具:ETL流程自动化
📋 gpload配置文件
创建gpload配置文件(gpload.yml):
# 📝 gpload配置文件示例
VERSION: 1.0.0.1
DATABASE: sales_db
USER: gpadmin
HOST: greenplum-master
PORT: 5432
GPLOAD:
INPUT:
- SOURCE:
LOCAL_HOSTNAME:
- greenplum-master
PORT: 8080
FILE:
- /data/sales/*.csv
- COLUMNS:
- order_id: INTEGER
- customer_id: INTEGER
- product_name: TEXT
- quantity: INTEGER
- unit_price: DECIMAL
- order_date: DATE
- sales_amount: DECIMAL
- FORMAT: csv
- DELIMITER: ','
- QUOTE: '"'
- HEADER: true
- ERROR_LIMIT: 100
- ERROR_TABLE: sales_errors
OUTPUT:
- TABLE: sales_data
- MODE: INSERT
PRELOAD:
- TRUNCATE: true
- REUSE_TABLES: true
SQL:
BEFORE: "SET statement_timeout = '30min';"
AFTER: "ANALYZE sales_data;"
执行gpload导入:
# !/bin/bash
# 🚀 gpload批量导入脚本
echo "🔄 开始执行gpload数据导入..."
# 设置环境变量
export PGPASSWORD=your_password
export MASTER_DATA_DIRECTORY=/data/master/gpseg-1
# 执行gpload
gpload -f /config/gpload_sales.yml -v
# 检查导入结果
if [ $? -eq 0 ]; then
echo "✅ 数据导入成功完成"
# 连接数据库验证结果
psql -h greenplum-master -U gpadmin -d sales_db -c "
SELECT
'sales_data' as table_name,
COUNT(*) as record_count,
MIN(order_date) as min_date,
MAX(order_date) as max_date
FROM sales_data;
"
else
echo "❌ 数据导入失败,请检查错误日志"
exit 1
fi
🔍 错误处理与监控
错误表分析:
-- 🔍 分析导入错误
SELECT
error_table,
error_count,
error_message,
COUNT(*) as occurrence_count
FROM (
SELECT
'sales_errors' as error_table,
1 as error_count,
gp_read_error_log('sales_data') as error_message
) error_analysis
GROUP BY error_table, error_count, error_message
ORDER BY occurrence_count DESC;
-- 📊 错误统计报告
WITH error_summary AS (
SELECT
DATE_TRUNC('hour', cmdtime) as error_hour,
COUNT(*) as error_count,
COUNT(DISTINCT filename) as affected_files
FROM gp_read_error_log('sales_data')
WHERE cmdtime >= CURRENT_DATE
GROUP BY DATE_TRUNC('hour', cmdtime)
)
SELECT
error_hour,
error_count,
affected_files,
ROUND(error_count * 100.0 / SUM(error_count) OVER(), 2) as error_percentage
FROM error_summary
ORDER BY error_hour;
🌐 外部表:实时数据访问
📡 创建外部表
外部表基础配置:
-- 🌐 创建外部CSV文件表
CREATE READABLE EXTERNAL TABLE ext_customer_data (
customer_id INT,
customer_name TEXT,
email TEXT,
phone TEXT,
address TEXT,
registration_date DATE
)
LOCATION ('file://greenplum-master:8080/data/customers/*.csv')
FORMAT 'csv' (
HEADER,
DELIMITER ',',
QUOTE '"'
)
ENCODING 'UTF8'
LOG ERRORS SEGMENT REJECT LIMIT 100 ROWS;
-- 🔗 创建外部数据库表
CREATE READABLE EXTERNAL TABLE ext_orders (
order_id INT,
customer_id INT,
order_amount DECIMAL(10,2),
order_status TEXT,
created_at TIMESTAMP
)
LOCATION ('pxf://orders_table?PROFILE=JDBC&SERVER=postgres')
FORMAT 'CUSTOM' (FORMATTER='pxfwritable_import');
-- 📊 使用外部表进行查询
SELECT
c.customer_name,
c.email,
COUNT(o.order_id) as order_count,
SUM(o.order_amount) as total_spent
FROM ext_customer_data c
LEFT JOIN ext_orders o ON c.customer_id = o.customer_id
WHERE c.registration_date >= '2024-01-01'
GROUP BY c.customer_id, c.customer_name, c.email
HAVING SUM(o.order_amount) > 1000
ORDER BY total_spent DESC;
🔄 可写外部表
数据导出外部表:
-- 📤 创建可写外部表用于导出
CREATE WRITABLE EXTERNAL TABLE export_sales_summary (
region TEXT,
quarter TEXT,
total_sales DECIMAL(15,2),
order_count INT,
avg_order_value DECIMAL(10,2)
)
LOCATION ('file://greenplum-master:8080/export/sales_summary.csv')
FORMAT 'csv' (
HEADER,
DELIMITER ',',
QUOTE '"'
)
ENCODING 'UTF8'
DISTRIBUTED RANDOMLY;
-- 📊 导出汇总数据
INSERT INTO export_sales_summary
SELECT
r.region_name as region,
EXTRACT(YEAR FROM s.order_date) || '-Q' || EXTRACT(QUARTER FROM s.order_date) as quarter,
SUM(s.sales_amount) as total_sales,
COUNT(*) as order_count,
AVG(s.sales_amount) as avg_order_value
FROM sales_data s
JOIN customer_regions r ON s.customer_id = r.customer_id
WHERE s.order_date >= '2024-01-01'
GROUP BY r.region_name, EXTRACT(YEAR FROM s.order_date), EXTRACT(QUARTER FROM s.order_date)
ORDER BY region, quarter;
📤 数据导出方法详解
🚀 COPY导出:高效数据提取
📊 基础导出操作
-- 📤 基础COPY导出示例
-- 1. 导出整个表
COPY sales_data
TO '/export/sales_backup.csv'
WITH (
FORMAT 'csv',
HEADER true,
DELIMITER ',',
QUOTE '"',
ENCODING 'UTF8'
);
-- 2. 导出查询结果
COPY (
SELECT
customer_id,
customer_name,
SUM(order_amount) as total_spent,
COUNT(*) as order_count
FROM customer_orders
WHERE order_date >= '2024-01-01'
GROUP BY customer_id, customer_name
ORDER BY total_spent DESC
LIMIT 1000
) TO '/export/top_customers.csv'
WITH (FORMAT 'csv', HEADER true);
-- 3. 导出到标准输出
COPY customer_summary
TO STDOUT
WITH (FORMAT 'csv', HEADER true);
🎯 高级导出技巧
分批导出大数据:
-- 🔄 分批导出脚本
DO $$
DECLARE
batch_size INT := 100000;
offset_val INT := 0;
total_rows INT;
batch_num INT := 1;
BEGIN
-- 获取总行数
SELECT COUNT(*) INTO total_rows FROM large_table;
WHILE offset_val < total_rows LOOP
-- 导出当前批次
EXECUTE format('
COPY (
SELECT * FROM large_table
ORDER BY id
LIMIT %s OFFSET %s
) TO ''/export/large_table_batch_%s.csv''
WITH (FORMAT ''csv'', HEADER true)',
batch_size, offset_val, batch_num
);
offset_val := offset_val + batch_size;
batch_num := batch_num + 1;
RAISE NOTICE '已导出批次 %,完成度:% %%',
batch_num - 1,
ROUND((LEAST(offset_val, total_rows)::NUMERIC / total_rows) * 100, 2);
END LOOP;
END $$;
压缩导出:
-- 📦 压缩导出数据
COPY (
SELECT * FROM sales_data
WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31'
) TO PROGRAM 'gzip > /export/sales_2024.csv.gz'
WITH (FORMAT 'csv', HEADER true);
-- 🗂️ 分区导出
DO $$
DECLARE
partition_name TEXT;
BEGIN
FOR partition_name IN
SELECT schemaname||'.'||tablename
FROM pg_tables
WHERE tablename LIKE 'sales_data_%'
LOOP
EXECUTE format('
COPY %s
TO ''/export/%s.csv''
WITH (FORMAT ''csv'', HEADER true)',
partition_name,
replace(partition_name, '.', '_')
);
END LOOP;
END $$;
🔧 gpextract工具:并行导出
⚡ 并行导出配置
gpextract命令示例:
# !/bin/bash
# 🚀 gpextract并行导出脚本
# 设置基本参数
DB_NAME="sales_db"
TABLE_NAME="sales_data"
EXPORT_DIR="/export/parallel"
DATE_FILTER="2024-01-01"
# 创建导出目录
mkdir -p ${EXPORT_DIR}
# 🔄 执行并行导出
gpextract \
--dbname ${DB_NAME} \
--table ${TABLE_NAME} \
--file ${EXPORT_DIR}/sales_data \
--format csv \
--delimiter ',' \
--quote '"' \
--header \
--where "order_date >= '${DATE_FILTER}'" \
--segment-file-prefix sales \
--compress gzip
# ✅ 验证导出结果
echo "📊 导出文件统计:"
ls -lh ${EXPORT_DIR}/sales_data*
echo "📈 记录数量验证:"
for file in ${EXPORT_DIR}/sales_data*.csv.gz; do
record_count=$(zcat "$file" | wc -l)
echo "$file: $((record_count - 1)) 条记录" # 减去标题行
done
🔄 数据同步策略
⏰ 增量同步机制
📅 基于时间戳的增量同步
-- 🕐 增量同步表设计
CREATE TABLE sync_log (
sync_id SERIAL PRIMARY KEY,
table_name VARCHAR(100),
last_sync_time TIMESTAMP,
sync_status VARCHAR(20),
record_count INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) DISTRIBUTED BY (sync_id);
-- 📊 增量导入存储过程
CREATE OR REPLACE FUNCTION incremental_sync_sales()
RETURNS TABLE(
sync_status TEXT,
new_records INT,
updated_records INT
) AS $$
DECLARE
last_sync TIMESTAMP;
new_count INT := 0;
update_count INT := 0;
BEGIN
-- 获取上次同步时间
SELECT COALESCE(MAX(last_sync_time), '1900-01-01'::TIMESTAMP)
INTO last_sync
FROM sync_log
WHERE table_name = 'sales_data'
AND sync_status = 'SUCCESS';
-- 创建临时表存储新数据
CREATE TEMP TABLE temp_new_sales AS
SELECT * FROM ext_source_sales
WHERE last_modified > last_sync;
GET DIAGNOSTICS new_count = ROW_COUNT;
-- 执行UPSERT操作
WITH upsert AS (
INSERT INTO sales_data
SELECT * FROM temp_new_sales
ON CONFLICT (order_id)
DO UPDATE SET
customer_id = EXCLUDED.customer_id,
product_name = EXCLUDED.product_name,
quantity = EXCLUDED.quantity,
unit_price = EXCLUDED.unit_price,
order_date = EXCLUDED.order_date,
sales_amount = EXCLUDED.sales_amount,
last_modified = EXCLUDED.last_modified
RETURNING (CASE WHEN xmax = 0 THEN 1 ELSE 0 END) as is_insert
)
SELECT
SUM(is_insert) as inserts,
SUM(1 - is_insert) as updates
INTO new_count, update_count
FROM upsert;
-- 记录同步日志
INSERT INTO sync_log (table_name, last_sync_time, sync_status, record_count)
VALUES ('sales_data', CURRENT_TIMESTAMP, 'SUCCESS', new_count + update_count);
-- 返回结果
RETURN QUERY SELECT
'SUCCESS'::TEXT as sync_status,
new_count as new_records,
update_count as updated_records;
EXCEPTION WHEN OTHERS THEN
INSERT INTO sync_log (table_name, last_sync_time, sync_status, record_count)
VALUES ('sales_data', CURRENT_TIMESTAMP, 'FAILED', 0);
RETURN QUERY SELECT
'FAILED: ' || SQLERRM as sync_status,
0 as new_records,
0 as updated_records;
END;
$$ LANGUAGE plpgsql;
-- 🔄 执行增量同步
SELECT * FROM incremental_sync_sales();
🔄 CDC(变更数据捕获)集成
-- 📡 CDC触发器设置
CREATE TABLE sales_changes (
change_id SERIAL PRIMARY KEY,
table_name VARCHAR(100),
operation_type VARCHAR(10), -- INSERT, UPDATE, DELETE
old_data JSONB,
new_data JSONB,
change_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
user_name VARCHAR(100)
) DISTRIBUTED BY (change_id);
-- 🔔 创建变更捕获触发器
CREATE OR REPLACE FUNCTION capture_sales_changes()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO sales_changes (table_name, operation_type, new_data, user_name)
VALUES (TG_TABLE_NAME, TG_OP, row_to_json(NEW), current_user);
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
INSERT INTO sales_changes (table_name, operation_type, old_data, new_data, user_name)
VALUES (TG_TABLE_NAME, TG_OP, row_to_json(OLD), row_to_json(NEW), current_user);
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO sales_changes (table_name, operation_type, old_data, user_name)
VALUES (TG_TABLE_NAME, TG_OP, row_to_json(OLD), current_user);
RETURN OLD;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
-- 应用触发器到表
CREATE TRIGGER sales_data_changes
AFTER INSERT OR UPDATE OR DELETE ON sales_data
FOR EACH ROW EXECUTE FUNCTION capture_sales_changes();
🔒 数据安全与验证
🛡️ 导入数据验证
✅ 数据质量检查
-- 🔍 数据质量验证函数
CREATE OR REPLACE FUNCTION validate_sales_data()
RETURNS TABLE(
check_name TEXT,
check_result TEXT,
affected_rows INT,
check_status TEXT
) AS $$
BEGIN
-- 1. 检查空值
RETURN QUERY
SELECT
'空值检查'::TEXT as check_name,
'关键字段存在空值'::TEXT as check_result,
COUNT(*)::INT as affected_rows,
CASE WHEN COUNT(*) > 0 THEN 'FAILED' ELSE 'PASSED' END as check_status
FROM sales_data
WHERE customer_id IS NULL
OR order_date IS NULL
OR sales_amount IS NULL;
-- 2. 检查数据范围
RETURN QUERY
SELECT
'数据范围检查'::TEXT as check_name,
'销售金额异常'::TEXT as check_result,
COUNT(*)::INT as affected_rows,
CASE WHEN COUNT(*) > 0 THEN 'FAILED' ELSE 'PASSED' END as check_status
FROM sales_data
WHERE sales_amount < 0 OR sales_amount > 1000000;
-- 3. 检查日期合理性
RETURN QUERY
SELECT
'日期合理性检查'::TEXT as check_name,
'订单日期不合理'::TEXT as check_result,
COUNT(*)::INT as affected_rows,
CASE WHEN COUNT(*) > 0 THEN 'FAILED' ELSE 'PASSED' END as check_status
FROM sales_data
WHERE order_date > CURRENT_DATE
OR order_date < '2020-01-01'::DATE;
-- 4. 检查重复数据
RETURN QUERY
SELECT
'重复数据检查'::TEXT as check_name,
'存在重复订单'::TEXT as check_result,
(COUNT(*) - COUNT(DISTINCT order_id))::INT as affected_rows,
CASE WHEN COUNT(*) != COUNT(DISTINCT order_id) THEN 'FAILED' ELSE 'PASSED' END as check_status
FROM sales_data;
END;
$$ LANGUAGE plpgsql;
-- 🎯 执行数据验证
SELECT * FROM validate_sales_data();
🔐 字段加密导入导出示例(不替代传输加密)
-- 🔒 加密导出敏感数据
CREATE OR REPLACE FUNCTION export_encrypted_customer_data()
RETURNS VOID AS $$
BEGIN
-- 导出加密后的客户数据
COPY (
SELECT
customer_id,
pgp_sym_encrypt(customer_name, 'encryption_key') as encrypted_name,
pgp_sym_encrypt(email, 'encryption_key') as encrypted_email,
pgp_sym_encrypt(phone, 'encryption_key') as encrypted_phone,
registration_date
FROM customer_data
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
) TO '/secure_export/encrypted_customers.csv'
WITH (FORMAT 'csv', HEADER true);
RAISE NOTICE '已完成加密客户数据导出';
END;
$$ LANGUAGE plpgsql;
-- 🔓 解密导入数据
CREATE OR REPLACE FUNCTION import_encrypted_customer_data()
RETURNS VOID AS $$
BEGIN
-- 创建临时表
CREATE TEMP TABLE temp_encrypted_customers (
customer_id INT,
encrypted_name TEXT,
encrypted_email TEXT,
encrypted_phone TEXT,
registration_date DATE
);
-- 导入加密数据
COPY temp_encrypted_customers
FROM '/secure_import/encrypted_customers.csv'
WITH (FORMAT 'csv', HEADER true);
-- 解密并插入到目标表
INSERT INTO customer_data (customer_id, customer_name, email, phone, registration_date)
SELECT
customer_id,
pgp_sym_decrypt(encrypted_name::BYTEA, 'encryption_key') as customer_name,
pgp_sym_decrypt(encrypted_email::BYTEA, 'encryption_key') as email,
pgp_sym_decrypt(encrypted_phone::BYTEA, 'encryption_key') as phone,
registration_date
FROM temp_encrypted_customers;
RAISE NOTICE '已完成加密客户数据导入并解密';
END;
$$ LANGUAGE plpgsql;
🚀 性能优化技巧
⚡ 导入性能优化
🎯 批量导入最佳实践
-- 🏗️ 优化导入性能的表结构
CREATE TABLE optimized_sales_data (
order_id BIGINT,
customer_id INT,
product_id INT,
quantity INT,
unit_price DECIMAL(10,2),
order_date DATE,
sales_amount DECIMAL(12,2)
)
WITH (
appendonly=true, -- 使用AO存储
compresstype=zstd, -- 启用压缩
compresslevel=3, -- 压缩级别
orientation=column -- 列式存储
)
DISTRIBUTED BY (customer_id)
PARTITION BY RANGE (order_date) (
PARTITION p2024q1 START ('2024-01-01') END ('2024-04-01'),
PARTITION p2024q2 START ('2024-04-01') END ('2024-07-01'),
PARTITION p2024q3 START ('2024-07-01') END ('2024-10-01'),
PARTITION p2024q4 START ('2024-10-01') END ('2025-01-01')
);
-- ⚡ 高性能导入配置
SET gp_autostats_mode = 'none'; -- 关闭自动统计信息收集
SET statement_timeout = '60min'; -- 设置语句超时
SET gp_enable_fast_sri = on; -- 启用快速单行插入
-- 📥 批量导入大文件
COPY optimized_sales_data
FROM '/data/large_sales_file.csv'
WITH (
FORMAT 'csv',
HEADER true,
DELIMITER ',',
FREEZE true, -- 请求冻结装载元组,并非跳过WAL;需确认版本、表类型及使用条件
OIDS false
);
-- 📊 导入后优化
ANALYZE optimized_sales_data; -- 收集统计信息
VACUUM optimized_sales_data; -- 清理空间
🔄 并行导入策略
# !/usr/bin/env python3
# 🚀 Python并行导入脚本
import psycopg2
import pandas as pd
from concurrent.futures import ThreadPoolExecutor
import os
from datetime import datetime
class GreenplumParallelImporter:
def __init__(self, host, database, user, password, port=5432):
self.connection_params = {
'host': host,
'database': database,
'user': user,
'password': password,
'port': port
}
def import_file_chunk(self, file_path, table_name):
"""导入单个文件块"""
try:
conn = psycopg2.connect(**self.connection_params)
cursor = conn.cursor()
# 使用COPY命令导入
with open(file_path, 'r', encoding='utf-8') as f:
cursor.copy_expert(
f"""COPY {table_name}
FROM STDIN
WITH (FORMAT 'csv', HEADER true, DELIMITER ',')""",
f
)
conn.commit()
cursor.close()
conn.close()
return {'file': file_path, 'status': 'success', 'time': datetime.now()}
except Exception as e:
return {'file': file_path, 'status': 'failed', 'error': str(e), 'time': datetime.now()}
def parallel_import(self, file_pattern, table_name, max_workers=4):
"""并行导入多个文件"""
import glob
files = glob.glob(file_pattern)
results = []
print(f"🚀 开始并行导入 {len(files)} 个文件到表 {table_name}")
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = [
executor.submit(self.import_file_chunk, file_path, table_name)
for file_path in files
]
for future in futures:
result = future.result()
results.append(result)
if result['status'] == 'success':
print(f"✅ {result['file']} 导入成功")
else:
print(f"❌ {result['file']} 导入失败: {result['error']}")
return results
# 使用示例
if __name__ == "__main__":
importer = GreenplumParallelImporter(
host='greenplum-master',
database='sales_db',
user='gpadmin',
password='your_password'
)
# 并行导入所有CSV文件
results = importer.parallel_import(
file_pattern='/data/sales/split_files/*.csv',
table_name='sales_data',
max_workers=8
)
# 统计结果
success_count = sum(1 for r in results if r['status'] == 'success')
print(f"📊 导入完成: {success_count}/{len(results)} 个文件成功")
🔧 实用工具与脚本
📝 数据导入导出监控
📊 实时监控脚本
-- 📈 导入导出监控视图
CREATE VIEW data_transfer_monitor AS
SELECT
schemaname,
tablename,
n_tup_ins as inserted_rows,
n_tup_upd as updated_rows,
n_tup_del as deleted_rows,
n_live_tup as current_rows,
n_dead_tup as dead_rows,
last_vacuum,
last_autovacuum,
last_analyze,
last_autoanalyze
FROM pg_stat_user_tables
WHERE schemaname NOT IN ('information_schema', 'pg_catalog')
ORDER BY n_tup_ins DESC;
-- 🔍 查看当前导入导出活动
SELECT
pid,
usename,
application_name,
client_addr,
state,
query_start,
state_change,
CASE
WHEN query LIKE '%COPY%FROM%' THEN 'DATA_IMPORT'
WHEN query LIKE '%COPY%TO%' THEN 'DATA_EXPORT'
ELSE 'OTHER'
END as operation_type,
LEFT(query, 100) as query_snippet
FROM pg_stat_activity
WHERE state = 'active'
AND query LIKE '%COPY%'
ORDER BY query_start;
🚨 错误监控与告警
# !/bin/bash
# 📡 数据传输监控脚本
MONITOR_LOG="/var/log/greenplum_monitor.log"
ERROR_THRESHOLD=10
EMAIL_ALERT="admin@company.com"
monitor_data_transfer() {
echo "$(date): 开始监控数据传输状态..." >> $MONITOR_LOG
# 检查gpfdist进程
gpfdist_count=$(ps aux | grep gpfdist | grep -v grep | wc -l)
echo "当前gpfdist进程数: $gpfdist_count" >> $MONITOR_LOG
# 检查导入错误
error_count=$(psql -t -A -c "
SELECT COUNT(*)
FROM gp_read_error_log('sales_data')
WHERE cmdtime >= CURRENT_TIMESTAMP - INTERVAL '1 hour'
" 2>/dev/null || echo "0")
echo "过去1小时错误数: $error_count" >> $MONITOR_LOG
# 错误告警
if [ "$error_count" -gt "$ERROR_THRESHOLD" ]; then
echo "⚠️ 警告: 数据导入错误数超过阈值($ERROR_THRESHOLD)" | \
mail -s "Greenplum数据导入告警" $EMAIL_ALERT
fi
# 检查磁盘空间
disk_usage=$(df /data | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$disk_usage" -gt 85 ]; then
echo "🔴 严重警告: 数据目录磁盘使用率${disk_usage}%,接近满载" | \
mail -s "Greenplum磁盘空间告警" $EMAIL_ALERT
fi
}
# 每5分钟检查一次
while true; do
monitor_data_transfer
sleep 300
done
💡 最佳实践总结
🎯 导入导出最佳实践
📋 性能优化清单
-
📥 导入优化
- 使用COPY命令而非INSERT语句
- 导入前删除不必要的索引
- 使用并行导入分割大文件
- 选择合适的分布键避免数据倾斜
-
📤 导出优化
- 使用分区导出减少锁定时间
- 利用压缩减少存储空间
- 并行导出提升速度
- 合理设置批次大小
-
🔒 安全考虑
- 敏感数据加密传输
- 设置适当的文件权限
- 记录操作审计日志
- 验证数据完整性
-
🔧 运维管理
- 建立完善的监控机制
- 制定数据恢复计划
- 定期清理临时文件
- 优化网络配置
🌟 故障排除指南
⚠️ 常见问题解决
| 问题类型 | 症状表现 | 解决方案 | 预防措施 |
|---|---|---|---|
| 内存不足 | 导入过程中断 | 增加内存或分批导入 | 监控内存使用率 |
| 磁盘空间满 | 写入失败 | 清理空间或扩容 | 设置磁盘告警 |
| 网络超时 | 连接中断 | 优化网络或增加超时 | 网络稳定性监控 |
| 权限问题 | 访问拒绝 | 检查文件和数据库权限 | 标准化权限管理 |
| 数据格式错误 | 解析失败 | 数据清洗和格式化 | 导入前数据验证 |
掌握了Greenplum数据导入导出技能,你就拥有了高效管理企业数据流转的核心能力!下一步,我们将学习表设计与分区策略,进一步提升数据处理效率。 🚀✨