Python实现 多进程导入CSV数据到 MySQL


Posted in Python onFebruary 26, 2017

前段时间帮同事处理了一个把 CSV 数据导入到 MySQL 的需求。两个很大的 CSV 文件, 分别有 3GB、2100 万条记录和 7GB、3500 万条记录。对于这个量级的数据,用简单的单进程/单线程导入 会耗时很久,最终用了多进程的方式来实现。具体过程不赘述,记录一下几个要点:

  1. 批量插入而不是逐条插入
  2. 为了加快插入速度,先不要建索引
  3. 生产者和消费者模型,主进程读文件,多个 worker 进程执行插入
  4. 注意控制 worker 的数量,避免对 MySQL 造成太大的压力
  5. 注意处理脏数据导致的异常
  6. 原始数据是 GBK 编码,所以还要注意转换成 UTF-8
  7. 用 click 封装命令行工具

具体的代码实现如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import codecs
import csv
import logging
import multiprocessing
import os
import warnings

import click
import MySQLdb
import sqlalchemy

warnings.filterwarnings('ignore', category=MySQLdb.Warning)

# 批量插入的记录数量
BATCH = 5000

DB_URI = 'mysql://root@localhost:3306/example?charset=utf8'

engine = sqlalchemy.create_engine(DB_URI)


def get_table_cols(table):
  sql = 'SELECT * FROM `{table}` LIMIT 0'.format(table=table)
  res = engine.execute(sql)
  return res.keys()


def insert_many(table, cols, rows, cursor):
  sql = 'INSERT INTO `{table}` ({cols}) VALUES ({marks})'.format(
      table=table,
      cols=', '.join(cols),
      marks=', '.join(['%s'] * len(cols)))
  cursor.execute(sql, *rows)
  logging.info('process %s inserted %s rows into table %s', os.getpid(), len(rows), table)


def insert_worker(table, cols, queue):
  rows = []
  # 每个子进程创建自己的 engine 对象
  cursor = sqlalchemy.create_engine(DB_URI)
  while True:
    row = queue.get()
    if row is None:
      if rows:
        insert_many(table, cols, rows, cursor)
      break

    rows.append(row)
    if len(rows) == BATCH:
      insert_many(table, cols, rows, cursor)
      rows = []


def insert_parallel(table, reader, w=10):
  cols = get_table_cols(table)

  # 数据队列,主进程读文件并往里写数据,worker 进程从队列读数据
  # 注意一下控制队列的大小,避免消费太慢导致堆积太多数据,占用过多内存
  queue = multiprocessing.Queue(maxsize=w*BATCH*2)
  workers = []
  for i in range(w):
    p = multiprocessing.Process(target=insert_worker, args=(table, cols, queue))
    p.start()
    workers.append(p)
    logging.info('starting # %s worker process, pid: %s...', i + 1, p.pid)

  dirty_data_file = './{}_dirty_rows.csv'.format(table)
  xf = open(dirty_data_file, 'w')
  writer = csv.writer(xf, delimiter=reader.dialect.delimiter)

  for line in reader:
    # 记录并跳过脏数据: 键值数量不一致
    if len(line) != len(cols):
      writer.writerow(line)
      continue

    # 把 None 值替换为 'NULL'
    clean_line = [None if x == 'NULL' else x for x in line]

    # 往队列里写数据
    queue.put(tuple(clean_line))
    if reader.line_num % 500000 == 0:
      logging.info('put %s tasks into queue.', reader.line_num)

  xf.close()

  # 给每个 worker 发送任务结束的信号
  logging.info('send close signal to worker processes')
  for i in range(w):
    queue.put(None)

  for p in workers:
    p.join()


def convert_file_to_utf8(f, rv_file=None):
  if not rv_file:
    name, ext = os.path.splitext(f)
    if isinstance(name, unicode):
      name = name.encode('utf8')
    rv_file = '{}_utf8{}'.format(name, ext)
  logging.info('start to process file %s', f)
  with open(f) as infd:
    with open(rv_file, 'w') as outfd:
      lines = []
      loop = 0
      chunck = 200000
      first_line = infd.readline().strip(codecs.BOM_UTF8).strip() + '\n'
      lines.append(first_line)
      for line in infd:
        clean_line = line.decode('gb18030').encode('utf8')
        clean_line = clean_line.rstrip() + '\n'
        lines.append(clean_line)
        if len(lines) == chunck:
          outfd.writelines(lines)
          lines = []
          loop += 1
          logging.info('processed %s lines.', loop * chunck)

      outfd.writelines(lines)
      logging.info('processed %s lines.', loop * chunck + len(lines))


@click.group()
def cli():
  logging.basicConfig(level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')


@cli.command('gbk_to_utf8')
@click.argument('f')
def convert_gbk_to_utf8(f):
  convert_file_to_utf8(f)


@cli.command('load')
@click.option('-t', '--table', required=True, help='表名')
@click.option('-i', '--filename', required=True, help='输入文件')
@click.option('-w', '--workers', default=10, help='worker 数量,默认 10')
def load_fac_day_pro_nos_sal_table(table, filename, workers):
  with open(filename) as fd:
    fd.readline()  # skip header
    reader = csv.reader(fd)
    insert_parallel(table, reader, w=workers)


if __name__ == '__main__':
  cli()

以上就是本文给大家分享的全部没人了,希望大家能够喜欢

Python 相关文章推荐
用Python实现web端用户登录和注册功能的教程
Apr 30 Python
Python实现二分查找算法实例
May 26 Python
Django框架中数据的连锁查询和限制返回数据的方法
Jul 17 Python
python中的错误处理
Apr 10 Python
基于python的七种经典排序算法(推荐)
Dec 08 Python
Python高级用法总结
May 26 Python
Python实现的txt文件去重功能示例
Jul 07 Python
如何使用python爬虫爬取要登陆的网站
Jul 12 Python
基于python操作ES实例详解
Nov 16 Python
python实现的分析并统计nginx日志数据功能示例
Dec 21 Python
TensorFlow使用Graph的基本操作的实现
Apr 22 Python
社区版pycharm创建django项目的方法(pycharm的newproject左侧没有项目选项)
Sep 23 Python
python检查URL是否正常访问的小技巧
Feb 25 #Python
python解析基于xml格式的日志文件
Feb 25 #Python
Python中防止sql注入的方法详解
Feb 25 #Python
Python 数据结构之旋转链表
Feb 25 #Python
Python数据结构之翻转链表
Feb 25 #Python
浅析python中SQLAlchemy排序的一个坑
Feb 24 #Python
python函数的5种参数详解
Feb 24 #Python
You might like
解析php二分法查找数组是否包含某一元素
2013/05/23 PHP
php生成N个不重复的随机数实例
2013/11/12 PHP
php实现aes加密类分享
2014/02/16 PHP
浅析PHP的静态成员函数效率更高的原因
2014/06/13 PHP
PHP比较运算符的详细介绍
2015/09/29 PHP
PHP使用GD库制作验证码的方法(点击验证码或看不清会刷新验证码)
2017/08/15 PHP
详解PHP swoole process的使用方法
2017/08/26 PHP
php封装单文件上传到数据库(路径)
2017/10/15 PHP
Firefox outerHTML实现代码
2009/06/04 Javascript
jQuery EasyUI API 中文文档 DateTimeBox日期时间框
2011/10/16 Javascript
JavaScript中valueOf函数与toString方法深入理解
2012/12/02 Javascript
JS调用CS里的带参方法实例
2013/08/01 Javascript
各浏览器对document.getElementById等方法的实现差异解析
2013/12/05 Javascript
jquery mobile的触控点击事件会多次触发问题的解决方法
2014/05/08 Javascript
浅谈Javascript如何实现匀速运动
2014/12/19 Javascript
JavaScript运动减速效果实例分析
2015/08/04 Javascript
JavaScript判断数组重复内容的两种方法(推荐)
2016/06/06 Javascript
JavaScript判断是否是微信浏览器
2016/06/13 Javascript
jquery Banner轮播选项卡
2016/12/26 Javascript
element中el-container容器与div布局区分详解
2020/05/13 Javascript
小程序组件传值和引入sass的方法(使用vant Weapp组件库)
2020/11/24 Javascript
[04:11]2014DOTA2国际邀请赛 CIS遗憾出局梦想不灭
2014/07/09 DOTA
Python中使用logging模块打印log日志详解
2015/04/05 Python
使用Python实现博客上进行自动翻页
2017/08/23 Python
利用Python进行数据可视化常见的9种方法!超实用!
2018/07/11 Python
python3.6的venv模块使用详解
2018/08/01 Python
python 从文件夹抽取图片另存的方法
2018/12/04 Python
python 阶乘累加和的实例
2019/02/01 Python
使用Python爬虫库requests发送表单数据和JSON数据
2020/01/25 Python
基于python连接oracle导并出数据文件
2020/04/28 Python
python中plt.imshow与cv2.imshow显示颜色问题
2020/07/16 Python
Python3如何实现Win10桌面自动切换
2020/08/11 Python
巴西独家产品和现场演示购物网站:Shoptime
2019/07/11 全球购物
美术兴趣小组活动总结
2014/07/07 职场文书
父母教会我观后感
2015/06/17 职场文书
对Golang中的FORM相关字段理解
2021/05/02 Golang