如何提高从AS400表读取Python的速度?
我们正在用Python开发一个数据摄取工具,在读取(多个)AS400表时遇到了速度缓慢的问题 这是该工具的核心部分(特定于读取AS400表格)。正如你所看到的,它做的不多;它只是从AS400数据库读取表,向其中添加一个时间戳列,然后将其写入另一个目标数据库,在本例中,该数据库位于MS SQL Server中如何提高从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
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
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:(或另一个包中的
)中的dfcursor.fetchmany
是最耗时的,这并不奇怪
仅获取前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包是否使用批量插入?或者一次插入多行?