Python脚本如何分批执行海量数据同步

wen python案例 32

本文目录导读:

Python脚本如何分批执行海量数据同步

  1. 基于LIMIT/OFFSET的分页分批(最常用)
  2. 基于游标的分批(更高效)
  3. 基于时间窗口的分批
  4. 完整的同步框架(带断点续传)
  5. 并行分批同步(多线程/多进程)
  6. 性能优化建议
  7. 关键配置示例
  8. 注意事项

对于海量数据同步,Python分批执行是必须的,否则会导致内存溢出或数据库连接超时,以下是几种常用的分批策略和实现方案:

基于LIMIT/OFFSET的分页分批(最常用)

适用于有自增ID或连续主键的表。

import time
from sqlalchemy import create_engine, text
def sync_data_in_batches(source_conn, target_conn, table_name, batch_size=10000):
    """
    分批同步数据
    """
    offset = 0
    total_synced = 0
    while True:
        # 分批查询源数据库
        query = f"""
        SELECT * FROM {table_name} 
        ORDER BY id 
        LIMIT {batch_size} OFFSET {offset}
        """
        with source_conn.connect() as conn:
            result = conn.execute(text(query))
            rows = result.fetchall()
        if not rows:
            break
        # 批量插入目标数据库
        with target_conn.connect() as conn:
            for row in rows:
                insert_query = f"""
                INSERT INTO {table_name} 
                VALUES ({','.join(['?' for _ in row])})
                ON CONFLICT(id) DO UPDATE SET ...
                """
                conn.execute(text(insert_query), row)
            conn.commit()
        total_synced += len(rows)
        offset += batch_size
        print(f"已同步: {total_synced} 条记录")
        time.sleep(0.1)  # 避免数据库压力过大
    return total_synced

基于游标的分批(更高效)

适用于大数据量,避免OFFSET的性能问题。

def sync_data_with_cursor(source_conn, target_conn, table_name, batch_size=10000):
    """
    使用游标分批同步
    """
    last_id = 0
    total_synced = 0
    while True:
        # 基于最后一次ID进行分批
        query = f"""
        SELECT * FROM {table_name} 
        WHERE id > {last_id}
        ORDER BY id 
        LIMIT {batch_size}
        """
        with source_conn.connect() as conn:
            result = conn.execute(text(query))
            rows = result.fetchall()
        if not rows:
            break
        # 准备批量插入
        batch_data = []
        for row in rows:
            batch_data.append(dict(row._mapping))
        # 批量写入目标库
        with target_conn.connect() as conn:
            # 使用executemany进行批量插入
            conn.execute(
                text(f"INSERT INTO {table_name} VALUES :data"),
                {"data": batch_data}
            )
            conn.commit()
        # 更新最后ID
        last_id = rows[-1][0]
        total_synced += len(rows)
        print(f"已同步: {total_synced} 条记录, 当前ID: {last_id}")
    return total_synced

基于时间窗口的分批

适用于按时间分区的数据同步。

from datetime import datetime, timedelta
def sync_data_by_time_window(source_conn, target_conn, table_name, 
                             start_time, end_time, window_days=1):
    """
    按时间窗口分批同步
    """
    current_start = start_time
    while current_start < end_time:
        current_end = min(current_start + timedelta(days=window_days), end_time)
        query = f"""
        SELECT * FROM {table_name} 
        WHERE created_at >= '{current_start}' 
        AND created_at < '{current_end}'
        ORDER BY created_at
        """
        with source_conn.connect() as conn:
            result = conn.execute(text(query))
            rows = result.fetchall()
        if rows:
            # 批量插入
            with target_conn.connect() as conn:
                # 使用pandas进行高效批量插入
                import pandas as pd
                df = pd.DataFrame(rows)
                df.to_sql(table_name, conn, if_exists='append', 
                         index=False, chunksize=5000)
            print(f"同步时间窗口: {current_start} - {current_end}, 记录数: {len(rows)}")
        current_start = current_end

完整的同步框架(带断点续传)

import json
import os
from datetime import datetime
class DataSyncManager:
    def __init__(self, config_file='sync_config.json'):
        self.config = self.load_config(config_file)
        self.checkpoint_file = 'sync_checkpoint.json'
    def load_config(self, config_file):
        """加载配置文件"""
        with open(config_file, 'r') as f:
            return json.load(f)
    def save_checkpoint(self, table_name, last_id, total_synced):
        """保存同步检查点"""
        checkpoint = {}
        if os.path.exists(self.checkpoint_file):
            with open(self.checkpoint_file, 'r') as f:
                checkpoint = json.load(f)
        checkpoint[table_name] = {
            'last_id': last_id,
            'total_synced': total_synced,
            'timestamp': str(datetime.now())
        }
        with open(self.checkpoint_file, 'w') as f:
            json.dump(checkpoint, f, indent=2)
    def load_checkpoint(self, table_name):
        """加载同步检查点"""
        if not os.path.exists(self.checkpoint_file):
            return None
        with open(self.checkpoint_file, 'r') as f:
            checkpoint = json.load(f)
        return checkpoint.get(table_name)
    def sync_table(self, source_config, target_config, table_name, 
                   batch_size=5000, resume=True):
        """
        执行表同步,支持断点续传
        """
        # 连接数据库
        source_conn = self.get_connection(source_config)
        target_conn = self.get_connection(target_config)
        try:
            # 检查断点
            last_id = 0
            if resume:
                checkpoint = self.load_checkpoint(table_name)
                if checkpoint:
                    last_id = checkpoint['last_id']
                    print(f"从断点恢复: last_id={last_id}")
            total_synced = 0
            retry_count = 0
            max_retries = 3
            while True:
                try:
                    # 分批查询
                    query = f"""
                    SELECT * FROM {table_name} 
                    WHERE id > {last_id}
                    ORDER BY id 
                    LIMIT {batch_size}
                    """
                    with source_conn.connect() as conn:
                        result = conn.execute(text(query))
                        rows = result.fetchall()
                    if not rows:
                        break
                    # 批量插入
                    with target_conn.connect() as conn:
                        for row in rows:
                            # 构建插入语句
                            columns = list(row._mapping.keys())
                            values = list(row._mapping.values())
                            insert_sql = f"""
                            INSERT INTO {table_name} ({','.join(columns)})
                            VALUES ({','.join(['?' for _ in values])})
                            ON CONFLICT(id) DO UPDATE SET ...
                            """
                            conn.execute(text(insert_sql), values)
                        conn.commit()
                    # 更新进度
                    last_id = rows[-1][0]
                    total_synced += len(rows)
                    retry_count = 0
                    # 保存检查点
                    self.save_checkpoint(table_name, last_id, total_synced)
                    print(f"表 {table_name}: 已同步 {total_synced} 条记录")
                except Exception as e:
                    retry_count += 1
                    if retry_count > max_retries:
                        raise
                    print(f"同步出错,重试 {retry_count}/{max_retries}: {e}")
                    time.sleep(5)
            print(f"表 {table_name} 同步完成,共 {total_synced} 条记录")
            return total_synced
        finally:
            source_conn.close()
            target_conn.close()
    def get_connection(self, config):
        """获取数据库连接"""
        from sqlalchemy import create_engine
        conn_str = f"{config['type']}://{config['user']}:{config['password']}@" \
                   f"{config['host']}:{config['port']}/{config['database']}"
        return create_engine(conn_str)

并行分批同步(多线程/多进程)

from concurrent.futures import ThreadPoolExecutor, as_completed
import threading
class ParallelDataSync:
    def __init__(self, max_workers=4):
        self.max_workers = max_workers
        self.lock = threading.Lock()
    def sync_table_parallel(self, source_config, target_config, table_name):
        """
        并行同步单表的不同分区
        """
        # 获取表的总ID范围
        source_conn = self.get_connection(source_config)
        with source_conn.connect() as conn:
            result = conn.execute(text(f"SELECT MIN(id), MAX(id) FROM {table_name}"))
            min_id, max_id = result.fetchone()
        # 计算分区
        total_records = max_id - min_id + 1
        chunk_size = total_records // self.max_workers
        futures = []
        with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
            for i in range(self.max_workers):
                start_id = min_id + i * chunk_size
                end_id = start_id + chunk_size if i < self.max_workers - 1 else max_id
                future = executor.submit(
                    self.sync_range,
                    source_config, target_config, table_name,
                    start_id, end_id
                )
                futures.append(future)
            total_synced = 0
            for future in as_completed(futures):
                total_synced += future.result()
        return total_synced

性能优化建议

def optimized_batch_insert(target_conn, table_name, rows, batch_size=5000):
    """
    优化批量插入性能
    """
    import psycopg2.extras
    # 使用copy_from(PostgreSQL)
    with target_conn.cursor() as cursor:
        # 准备CSV数据
        import io
        csv_buffer = io.StringIO()
        writer = csv.writer(csv_buffer)
        for row in rows:
            writer.writerow(row)
        csv_buffer.seek(0)
        cursor.copy_from(csv_buffer, table_name, sep=',', null='')
        target_conn.commit()
    # 或使用批量插入优化
    with target_conn.cursor() as cursor:
        psycopg2.extras.execute_values(
            cursor,
            f"INSERT INTO {table_name} VALUES %s",
            rows,
            template=None,
            page_size=batch_size
        )
        target_conn.commit()

关键配置示例

{
  "source": {
    "type": "postgresql",
    "host": "source_host",
    "port": 5432,
    "database": "source_db",
    "user": "user",
    "password": "password"
  },
  "target": {
    "type": "postgresql",
    "host": "target_host",
    "port": 5432,
    "database": "target_db",
    "user": "user",
    "password": "password"
  },
  "sync_settings": {
    "batch_size": 10000,
    "parallel_workers": 4,
    "retry_times": 3,
    "checkpoint_enabled": true
  }
}

注意事项

  1. 事务管理:每批处理完及时提交,避免长事务
  2. 错误处理:实现重试机制和错误日志
  3. 性能监控:记录每批处理时间和速度
  4. 资源控制:监控内存使用,避免OOM
  5. 数据一致性:使用事务确保原子性
  6. 索引优化:大批量插入前暂时禁用索引,插入后重建

选择哪种策略取决于你的数据特点和业务需求,通常建议从简单的LIMIT/OFFSET实现开始,根据实际性能进行优化。

抱歉,评论功能暂时关闭!