Neo4j Python异步驱动性能不及同步驱动的原因排查
我希望在Python中并发执行Neo4j查询,因此编写了测试代码对比异步驱动和同步驱动的性能,但结果不符合预期:异步执行耗时反而比同步更长。
测试代码
import asyncio from neo4j import GraphDatabase from neo4j import AsyncGraphDatabase import time driver = GraphDatabase.driver(...) async_driver = AsyncGraphDatabase.driver(...) async def async_execute(): records, _, _ = await async_driver.execute_query("MATCH (n) RETURN n LIMIT 10000", database_="...") rec_list = [record for record in records] async def async_time(n): start = time.time() await asyncio.gather(*(async_execute() for _ in range(n))) end = time.time() return end - start def normal_time(n): start = time.time() for _ in range(n): records, _, _ = driver.execute_query("MATCH (n) RETURN n LIMIT 10000", database_="...") rec_list = [record for record in records] end = time.time() return end - start async def main(): async_runtime = 0 normal_runtime = 0 total_run = 10 for _ in range(total_run): async_runtime += await async_time(2) normal_runtime += normal_time(2) print(f"async_runtime = {async_runtime/total_run}") print(f"normal_runtime = {normal_runtime/total_run}") if __name__ == "__main__": asyncio.run(main())
测试结果
async_runtime = 7.239475727081299 normal_runtime = 4.628250503540039
我原本预期async_runtime应小于normal_runtime,但实际结果相反,请问可能的原因是什么?
环境配置
% pip show neo4j Name: neo4j Version: 5.18.0 % neo4j --version 5.18.1
查询执行计划(PROFILE结果)
+--------------------------------------------------------------------------------------------------+ | Plan | Statement | Version | Planner | Runtime | Time | DbHits | Rows | Memory (Bytes) | +--------------------------------------------------------------------------------------------------+ | "PROFILE" | "READ_ONLY" | "" | "COST" | "SLOTTED" | 1665 | 60001 | 10000 | 64 | +--------------------------------------------------------------------------------------------------+ Planner COST Runtime SLOTTED Runtime version 5.18 +-----------------+----+---------+----------------+-------+---------+----------------+------------------------+ | Operator | Id | Details | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | +-----------------+----+---------+----------------+-------+---------+----------------+------------------------+ | +ProduceResults | 0 | n | 10000 | 10000 | 50000 | 0 | 0/0 | | | +----+---------+----------------+-------+---------+----------------+------------------------+ | +Limit | 1 | 10000 | 10000 | 10000 | 0 | | 0/0 | | | +----+---------+----------------+-------+---------+----------------+------------------------+ | +AllNodesScan | 2 | n | 10000 | 10000 | 10001 | | 0/0 | +-----------------+----+---------+----------------+-------+---------+----------------+------------------------+ Total database accesses: 60001, total allocated memory: 64 10000 rows ready to start consuming query after 447 ms, results consumed after another 1218 ms
可能的原因分析
1. 并发度不足,异步开销抵消优势
测试仅并发执行2次查询,异步驱动本身存在协程调度、连接初始化等额外开销,当并发数量较小时,这些开销会超过异步并行带来的收益。同步驱动在低并发场景下没有这些调度开销,表现反而更好。建议提高并发数(比如20+)再测试,此时异步的并行优势才会显现。
2. 查询瓶颈在数据库端
从执行计划看,查询是全节点扫描并返回10000条数据,数据库端执行时间(约1.6秒)远大于网络传输和客户端处理时间。此时不管异步还是同步,瓶颈都在Neo4j服务器的处理能力上——服务器同时处理多个查询时,会因CPU、磁盘IO等资源竞争导致总耗时增加,异步只是让客户端并行发起请求,但服务器无法并行处理更多请求的话,优势就发挥不出来。
3. 异步驱动连接池配置不合理
默认情况下,异步驱动的连接池大小可能和同步驱动不同。如果连接池最大连接数小于并发查询数,异步请求会排队等待连接,反而增加等待时间。可以显式配置连接池参数:
async_driver = AsyncGraphDatabase.driver(..., max_connection_pool_size=10)
确保连接池足够容纳并发查询数量。
4. 客户端数据处理的同步开销
async_execute中将records转为列表的操作rec_list = [record for record in records]是同步的,数据量较大时,这部分同步处理的开销会抵消异步的并行优势。可以尝试去掉这部分代码,只执行查询不处理结果,看是否能体现异步的性能提升。
5. 测试方法的误差
测试交替执行异步和同步任务,可能受系统资源(CPU、网络)波动影响。建议分开测试:先单独跑10次异步测试,再单独跑10次同步测试,避免互相干扰;同时增加单次测试的循环次数,减少误差。
内容的提问来源于stack exchange,提问作者Pond-nj

