Python执行SQL查询远慢于SQL客户端加sleep后提速问题排查求助
问题原因分析
- 事务未提交导致额外开销:执行完bulk inserts后没有显式提交写入事务,Python端的数据库连接默认关闭自动提交,此时写入操作产生的行锁、表级意向锁没有释放,后续查询需要等待锁释放,同时MVCC机制需要遍历大量undo日志生成一致性视图,产生极高的额外开销。等待120秒后事务超时自动提交/回滚,锁被释放、undo日志清理,查询速度就恢复正常。
- 数据库统计信息未更新:批量写入10k-60k行属于数据量大幅变动,数据库查询优化器依赖的表统计信息还是旧版本,会生成错误的执行计划,比如不走索引走全表扫描。等待一段时间后数据库后台自动触发统计信息收集,执行计划恢复正常,查询速度变快。
- 会话配置差异:Python端的数据库连接配置和Datagrip存在明显差异:
- 结果集拉取批次(fetch size)过小:SQLAlchemy默认fetch size极低,若查询返回大量结果,需要和数据库进行多次网络往返,Datagrip默认会设置较大的fetch size减少往返次数。
- 事务隔离级别过高:Python端连接可能默认使用REPEATABLE READ隔离级别,而Datagrip使用READ COMMITTED,前者需要更多的一致性检查开销。
- 执行计划缓存问题:Python端参数化查询可能生成了错误的缓存执行计划,而Datagrip执行时会重新生成正确的执行计划。
优化方案
- 写入后显式提交事务
执行完bulk inserts后立刻调用session.commit()或者conn.commit(),确保写入事务正常结束,释放所有锁资源,清理undo日志,不要依赖超时自动提交。 - 手动触发统计信息更新
批量写入完成后,执行对应表的统计信息收集命令,MySQL用ANALYZE TABLE etldb2.bill;,PostgreSQL用ANALYZE etldb2.bill;,让优化器生成正确的执行计划。 - 调整连接会话配置
- 设置合适的fetch size:执行查询前配置拉取批次,示例如下:
# session 方式 bill_ids = session.execute(query_bill_id).execution_options(stream_results=True, max_row_buffer=1000).all() # 连接方式 with self.engine.connect() as conn: bill_ids = conn.execution_options(stream_results=True, max_row_buffer=1000).execute(query_bill_id) - 显式设置较低的事务隔离级别:创建引擎时指定隔离级别为READ COMMITTED:
engine = create_engine(DB_URL, isolation_level="READ COMMITTED")
- 设置合适的fetch size:执行查询前配置拉取批次,示例如下:
- 优化查询本身
当前的查询可以简化避免子查询,同时确认identity、upload_meta_id、status三个字段已经建立联合索引,索引建议:
CREATE INDEX idx_bill_meta_status ON etldb2.bill (upload_meta_id, status, identity, id);
优化后的查询可以改写为:
SELECT prev.id FROM etldb2.bill cur INNER JOIN etldb2.bill prev ON cur.identity = prev.identity WHERE cur.upload_meta_id = 2001 AND prev.upload_meta_id = 1 AND prev.status = 1
内容的提问来源于stack exchange,提问作者user3583807
相关产品推荐
相关产品推荐

