如何提高从AS400表读取Python的速度?

如何提高从AS400表读取Python的速度?,python,python-3.x,performance,db2,ibm-midrange,Python,Python 3.x,Performance,Db2,Ibm Midrange,我们正在用Python开发一个数据摄取工具,在读取(多个)AS400表时遇到了速度缓慢的问题 这是该工具的核心部分(特定于读取AS400表格)。正如你所看到的,它做的不多;它只是从AS400数据库读取表,向其中添加一个时间戳列,然后将其写入另一个目标数据库,在本例中,该数据库位于MS SQL Server中 def process(record): engine_source = new_as400_connection() engine_destination = new_db

我们正在用Python开发一个数据摄取工具,在读取(多个)AS400表时遇到了速度缓慢的问题

这是该工具的核心部分(特定于读取AS400表格)。正如你所看到的,它做的不多;它只是从AS400数据库读取表,向其中添加一个时间戳列,然后将其写入另一个目标数据库,在本例中,该数据库位于MS SQL Server中

def process(record):
    engine_source = new_as400_connection()
    engine_destination = new_db_connection()
    ROWS = 100_000
    
    df_gen = pd.read_sql(record.query, engine_source, chunksize=ROWS)
    for df in df_gen:
        df['_ETLDate'] = pd.to_datetime('today').replace(microsecond=0)
        df.to_sql(
            record.target,
            engine_destination,
            if_exists='append',
            index=False,
        )
    
    engine_source.dispose()
    engine_destination.dispose()
下面是我们如何使用内置的
ThreadPoolExecutor
调用此函数:

if __name__ == '__main__':
    with concurrent.futures.ThreadPoolExecutor(16) as executor:
        __ = executor.map(process, records)
其中,
记录
是要处理的表列表,如下所示:

┌───────────┬───────────┬────────────────────────────────────────┐
│ source_id │  target   │                 query                  │
├───────────┼───────────┼────────────────────────────────────────┤
│ TABLE001  │ target_01 │ SELECT * FROM TABLE001                 │
│ TABLE002  │ target_02 │ SELECT * FROM TABLE002                 │
│ TABLE003  │ target_03 │ SELECT * FROM TABLE003 WHERE ABC > 100 │
└───────────┴───────────┴────────────────────────────────────────┘
显然,其想法是并行地将数据从AS400复制到SQL Server。但是,这种方法比使用SSIS包执行的等效作业要慢。区别非常显著:此Python代码比SSIS包花费的时间多3-4倍(即使后者是按顺序执行的)

我们尝试过的事情:

  • 使用多重处理。这一点也不奇怪,因为这应该是一个I/O密集型操作
  • 查看AS400文档:非常无用
  • 每个线程中有多个线程—例如,尝试读取一个线程中的第1-10K行,另一个线程中的下10K行,依此类推。差别不大
  • SQL Server驻留在Azure环境中,而AS400数据库驻留在本地服务器中。Python工具最初是从Azure VM执行的,我们认为将代码执行转移到本地服务器会有所帮助。令人惊讶的是,它比Azure VM服务器中的执行时间稍长
  • 一次获取所有行,而不是批量获取100K。这并没有帮助,但有时,一些大型表会导致内存错误,Python会崩溃
  • 按顺序运行所有表,而不是并行运行。只需要比多线程方法稍长一点(这并不奇怪)
  • 我们还使用了另一个类似的包,其中我们使用的不是数据帧,而是
    pyodbc
    cursor.fetchmany
    。它的性能几乎与此相同。因此,我们相信这种严重的缓慢不是由于使用了
    pandas.read\u sql
一位同事建议在子shell中的单独进程中调用每个表的复制进程。因此,我们从多线程方法切换到这个子进程方法:

if __name__ == '__main__':
    args = parser.parse_args()
    if args.single_record:
        process(json.loads(record))
    else:
        cmd = './venv/Scripts/python -m sourcetostaging --single_record'
        for record in records:
            subprocess.Popen(
                [*cmd.split(), json.dumps(record)],
                cwd='./',
                stdout=subprocess.DEVNULL,
            )
它运行良好:我们看到了大约60%-70%的改善。但是,此代码仍然比SSIS包慢:此修改的方法花费的时间是SSIS包的两倍(即使后者是按顺序执行的)

我们理解,因为SSIS使用SQL,所以它总是比同等的Python代码快。然而,再多花2倍的时间似乎表明我在Python代码中做了一些错误的事情

以下是我们正在使用的连接字符串:

def new_as400_connection():
    cs = 'ibm_db_sa+pyodbc:///?odbc_connect=' + quote(
        'DRIVER={iSeries Access ODBC Driver};'
        + 'SYSTEM={system};UID={uid};PWD={pwd}'.format(**config_as400)
    )
    return sqlalchemy.create_engine(cs)


def new_db_connection():
    cs = 'mssql+pyodbc://{user}:{pwd}@{host}/{database}?driver={driver}'
    return sqlalchemy.create_engine(
        cs.format(**config_db),
        fast_executemany=True,
    )
还有其他我们忽略的方法吗?我们认为多线程方法非常常见

注释

  • Python和SSI都使用同一组配置(Compression=True等)
  • 我们试着分析这段代码,看看瓶颈在哪里:df_gen:(或另一个包中的
    cursor.fetchmany
    )中的df
    是最耗时的,这并不奇怪

我猜性能问题与ODBC有关

我将编写一个web服务,该服务按键返回表行的子集。下面是PHP代码,它通过客户代码从产品主控中读取行。您可以用python或node编写类似的web服务

web服务的一个优点是,您可以专门为正在阅读的表编写代码。如果读取整个库存文件,您可以从项目编号开始,然后
仅获取前50000行
。当读取下一批时,客户机可以告诉服务器web服务从何处开始读取

<?php

header("Content-type: text/javascript; charset:utf-8;");

$cucode = isset($_GET["cucode"]) ? $_GET["cucode"]: '' ; 

$sql = 'select    a.prcucd, a.prprcd, a.prdes1, a.prdes2 ' . 
        'from     prmast a ' . 
        "where    ( ? = ' ' or a.prcucd = ? ) " ; 
$libl = 'qgpl qtemp' ;
$conn = as400Connect( $libl ) ;                    
$stmt = db2_prepare($conn, $sql);                          
   
db2_bind_param( $stmt, 1, "cucode", DB2_PARAM_IN ) ;       
db2_bind_param( $stmt, 2, "cucode", DB2_PARAM_IN ) ;       
                                                           
$result = as400Execute($stmt, $sql) ;

$ar1 = array( ) ;
while( $row = db2_fetch_assoc( $stmt ))
{
  $ar1[] = $row ;
}

echo json_encode($ar1);                          `          

 
// ---------------------------- as400Connect ------------------------
function as400Connect( $libl )
{
  $options = array('i5_naming' => DB2_I5_NAMING_ON);  
  if (strlen($libl) > 0)
  {
    $options['i5_libl'] = $libl ;
  }
  $conn = db2_connect("*LOCAL","","", $options);
  if (!$conn) {
    echo "Connection failed" ;
    echo "<br>" ;
    echo db2_conn_errormsg( ) ;
    exit( ) ;
  }
  return $conn ;
}

// ------------------------- as400Execute --------------------------
function as400Execute( $stmt, $sql = null )
{
  $result = db2_execute($stmt) ;
  if (!$result) {
    echo '<br>' . 'the db2 execute failed. ' ;
    echo '<br>' . ' SQLSTATE: ' . db2_stmt_error( ) ;
    echo '<br>' . ' message: ' . db2_stmt_errormsg( ) ;
    if       (is_null($sql) == false )
      echo '<br>' . $sql ;

  // write to the php error log.
    $fullText = 'db2_execute failed. sqlstate:' . db2_stmt_error( ) .
                ' message: ' . db2_stmt_errormsg( ) ;
    if       (is_null($sql) == false )
      $fullText   = $fullText . ' ' . $sql ;
    error_log( $fullText ) ;
    error_log(print_r(debug_backtrace(), TRUE));
  }

return     $result ;
}

?>

如果您希望您的解决方案的性能接近专有SSIS解决方案的性能,那么您的设计在批量I/O方面需要与专有设计相似或更好。@mao,听起来很公平。您建议的任何资源都可以帮助我学习与并行性、大容量I/O等相关的更好的设计?研究Db2客户机软件的文档,检查存在哪些大容量导出和大容量卸载功能,以及在导出/卸载期间存在哪些选项来优化网络流量。有时,在本地大容量加载到目标之前,卸载到平面文件(或管道)并转换这些文件是最快的方法。我将查看如何将行插入到目标中。您的SSIS包是否使用批量插入?或者一次插入多行?