V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
ErenJaeger
V2EX  ›  Python

感觉 aiomysql,异步执行多个查询,性能并没有显著的提升啊

  •  
  •   ErenJaeger · 12 天前 · 1936 次点击

    代码如下:

     async def query(self, query, param=None):
            conn, cur = await self.getCursor()
            try:
                await cur.execute(query, param)
                return await cur.fetchall()
            except:
                print('error')
            finally:
                if cur:
                    await cur.close()
                await self.pool.release(conn)
    
    # mysqlobj 是 aiomysql 连接池对象
    
    result = await asyncio.gather(mysqlobj.query(sql1), mysqlobj.query(sql2), mysqlobj.query(sql3), mysqlobj.query(sql4), mysqlobj.query(sql5))
    
    

    在我的理解中,如果异步执行的话,这段代码执行的时长应该是这 5 个 sql 中耗时最长的时长,但是测试多次,相对同步执行这 5 个 sql 来说,执行时间并没有显著的提升。各位大佬能指点一下吗?

    40 条回复    2021-07-22 16:06:37 +08:00
    BBCCBB
        1
    BBCCBB   12 天前
    你这代码逻辑上还是`同步`的, 因为你有 await, 会等待每个 sql 执行完成, 就是说一个 sql 执行完后才会去执行下一步的 sql. 只是你这个 sql 还没执行完的过程中, 线程不会卡在这里,, 而是会去执行其他的异步 task. 等你 await 的 task 返回后再继续执行你这个 task.

    异步不是你理解的这样的异步. asyncio 的好处是异步 io 的并发..
    chaleaoch
        2
    chaleaoch   12 天前
    同一个 async 中的 await 是顺序执行的要不然不乱套了吗?
    ErenJaeger
        3
    ErenJaeger   12 天前
    @BBCCBB 你的意思是 await asyncio.gather()中执行的 sql,还是会顺序执行下去?而不是同时进行查询,查询完毕后返回? 之前写 node.js 的时候,后续处理的步骤都写在回调过程中了,耗时操作不会阻塞,而是去执行下面的步骤了。
    ErenJaeger
        4
    ErenJaeger   12 天前
    @chaleaoch await 语法糖的作用应该就是等待异步代码返回结果了吧,类似于回调。我想问的是 asyncio.gather()里面的任务不应该是并发执行的吗
    chaleaoch
        5
    chaleaoch   12 天前
    @ErenJaeger 是并发的没错, 但是 是 task 并发.
    协程的基础是生成器 - - 如果从生成器开始看的话 应该会有更好的理解- -
    chaleaoch
        6
    chaleaoch   12 天前
    如图
    是 task1 和 task2 之间 切换 和其他的 await 没关系

    我手画的 - - 偷懒没画全 希望我表达清楚了
    https://cdn.jsdelivr.net/gh/chaleaoch/[email protected]/images/1626859549049-1626859549045.png
    v2exblog
        7
    v2exblog   12 天前
    tasks = [asyncio.create_task(mysqlobj.query(sql1)),asyncio.create_task(mysqlobj.query(sql2)),asyncio.create_task(mysqlobj.query(sql3)),asyncio.create_task(mysqlobj.query(sql4)),asyncio.create_task(mysqlobj.query(sql5))]

    await asyncio.wait(tasks)
    这样试试呢
    BBCCBB
        8
    BBCCBB   12 天前
    nodejs 继续执行后面是因为你不用 await nodejs 的这个 promise, 而是等他返回结果返回后, 调用你注册的回调 function.去处理 function 果返回

    nodejs 里, 如果你后续的执行依赖某个 nodejs 异步函数的结果, 那不还是得等异步函数结束拿到结果后再执行后面的结果吗.


    1:
    var s = asyncFunc(xxx, function (res) {
    res 是这个函数的返回值.
    }); // 不需要等待 asyncFunc 返回值,

    xxx(); //


    2:
    var res = await asyncFunc(xxx); // 需要等待 asyncFunc 返回值
    console.log(res)
    xxx()

    类似这两种方式.
    pabupa
        9
    pabupa   12 天前
    楼主这种用法没有问题。
    pabupa
        10
    pabupa   12 天前
    pabupa
        11
    pabupa   12 天前
    我觉得应该是驱动的问题,,,
    pabupa
        12
    pabupa   12 天前
    @pabupa 比如你的连接池中的连接可能太小了……
    ErenJaeger
        13
    ErenJaeger   12 天前
    @chaleaoch 是呀,就是我指的就是 gather 里面的任务并发执行的话,执行时长应该是这里面最长执行任务的时长,而不是所有任务执行时长的累积
    Vegetable
        14
    Vegetable   12 天前
    用法没问题,不过信息还是不够。包括链接池大小和具体时常。甚至说,mysql 的性能是不是瓶颈,都需要考虑。
    ErenJaeger
        15
    ErenJaeger   12 天前
    @BBCCBB 是的,这两种方式是写法的区别。 我的意思是:
    fuction outside(){
    asyncFucn1()
    asyncFunc2()
    asyncFunc3()
    asycnFunc4()
    ......
    }
    如果 outside 函数等待里面异步函数结束退出的话,执行时长应该是内部异步函数执行时间最长的时长吧。那我同时发起 N 个 sql 查询,整体查询时长应该是 N 个查询 sql 中执行时长最长的时长吧
    ErenJaeger
        16
    ErenJaeger   12 天前
    @pabupa mix 是 5,max 是 10,按理说应该够了,我调整一下试试
    ErenJaeger
        17
    ErenJaeger   12 天前
    @v2exblog 试了试,差不多,timeit 测试了下跟同步查询的差不多,就感觉很奇怪
    chaleaoch
        18
    chaleaoch   12 天前
    @ErenJaeger 换成 100 次查询试一下.
    BBCCBB
        19
    BBCCBB   12 天前
    sorry, 理解错了.. 光看了你问题里写的 5 个 sql, 我漏看了下面的 gather, 你要问的是 gather 里的这 5 个 task 吧
    BBCCBB
        20
    BBCCBB   12 天前
    刚好你这个 task 里也是 5 个 sql 的 await.. :(
    ErenJaeger
        21
    ErenJaeger   12 天前
    @chaleaoch 淦,我调了 1000 次,同步 40 多秒,异步 5 秒多。问题生产环境中一个接口里面不可能会有这么高频次的查询,小频次的查询,同步异步的差距就很不明显
    BBCCBB
        22
    BBCCBB   12 天前
    次数调大点试试. 看起来没啥问题. 可以把代码贴完整点, 包括 loop.run_until_complete()这一块
    BBCCBB
        23
    BBCCBB   12 天前
    一个接口不可能这么高, 但是其他的接口也有网络 io, 和你测一个接口 1000 次概念差不多, 并发上去了, asyncio 性能差异就出来了..

    如果只是一个接口, 没啥访问, 直接同步搞.. 简单
    ErenJaeger
        24
    ErenJaeger   12 天前
    @BBCCBB gather 里面是顺序执行的吗?我看官方文档里是这样描述的:
    同时运行 aws 序列中的可等待对象。

    如果 aws 中的任何 awaitable 是协程,则它会自动安排为任务。

    如果所有 awaitable 都成功完成,则结果是返回值的聚合列表。结果值的顺序对应于 aws 中等待的顺序。

    如果 return_exceptions 为 False (默认),第一个引发的异常会立即传播到在 gather() 上等待的任务。aws 序列中的其他等待对象不会被取消,而是会继续运行。

    如果 return_exceptions 为 True,则将异常视为成功结果,并在结果列表中聚合。

    如果 gather() 被取消,所有提交的等待(尚未完成)也将被取消。

    如果 aws 序列中的任何 Task 或 Future 被取消,则将其视为引发了 CancelledError - 在这种情况下不会取消 gather() 调用。这是为了防止取消一个提交的任务 /未来导致其他任务 /未来被取消。
    ErenJaeger
        25
    ErenJaeger   11 天前
    class Pmysql:

    def __init__(self):
    self.conn = None
    self.pool = None

    async def initpool(self):
    try:
    __pool = await aiomysql.create_pool(minsize=10,
    maxsize=10,
    host=Config.host,
    port=Config.port,
    user=Config.user,
    password=Config.password,
    db='db')
    return __pool
    except Exception as e:
    print(e)
    print('create connect error.')

    async def getCursor(self):
    conn = await self.pool.acquire()
    cur = await conn.cursor()
    return conn, cur

    async def query(self, query, param=None):
    conn, cur = await self.getCursor()
    try:
    await cur.execute(query, param)
    return await cur.fetchall()
    except:
    print('error')
    finally:
    if cur:
    await cur.close()
    await self.pool.release(conn)

    async def getAmysqlobj():
    mysqlobj = Pmysql()
    pool = await mysqlobj.initpool()
    mysqlobj.pool = pool
    return mysqlobj
    BBCCBB
        26
    BBCCBB   11 天前
    gather 不是顺序执行, 都 asyncio 了, 只要里面 task 不阻塞, 就是异步执行的.

    你这个没啥问题, 根据你加大到 1000 次, 差距挺大的, 所以应该是量不够大.
    ErenJaeger
        27
    ErenJaeger   11 天前
    @BBCCBB 其实最大的需求,还是提升响应效率了,这个查询 2s 多能降到 1s 多,甚至不到 1s,就是最高的期望值了,但是测试感觉难以实现
    BBCCBB
        28
    BBCCBB   11 天前
    asyncio 不能降低你代码里单个 query 方法的耗时, 他要做的是用少量线程就能支撑超高的并发量,, 这个用线程是很难实现的, 单个请求的响应时间并不会变得更快,
    vindurriel
        29
    vindurriel   11 天前 via iPhone
    sql 是啥 可以换成 sleep(1) 看是 n 秒返回还是 1 秒多返回
    myCupOfTea
        30
    myCupOfTea   11 天前
    楼主写法没问题,怎么好多人说不是并发 gather 不就是并发吗
    myCupOfTea
        31
    myCupOfTea   11 天前
    但是这类数据库的库,其实异步并发不会比多线程好
    因为这类库很多都是底层用的 run_in_excutor,其实就是跑线程池(还是要看具体代码,至少 mongodb 官方的异步库是这样的)
    ErenJaeger
        32
    ErenJaeger   11 天前
    @BBCCBB 是呀,我本来是想着,并发执行多个 sql,这样将整体查询时长降低至最长查询的那个 sql 。但是感觉并没有达到预期的效果
    RockShake
        33
    RockShake   11 天前
    异步并不是多线程啊,异步是模拟 Javascript 的逻辑,将程序操作简化为一个线程,使用这个逻辑可以实现 await 功能,详见: https://ruanyifeng.com/blog/2019/11/python-asyncio.html
    xxfye
        34
    xxfye   11 天前   ❤️ 1
    @ErenJaeger 你对异步的理解错了,异步是让一个餐馆合理分配上菜时间使其能够装下更多的顾客,而不是让每个顾客等待上菜的时间减少。
    www5070504
        35
    www5070504   11 天前
    等待时间应该是最长的那个吧 比原来顺序执行肯定短了不少 但是也不可能减少最长的那个请求的时间
    forbxy
        36
    forbxy   11 天前
    python 的协程,包括这个库只是为了单线程服务器在处理 mysql 不会卡住,可以切到其他用户的连接处理
    niu0619
        37
    niu0619   11 天前
    ErenJaeger
        38
    ErenJaeger   11 天前
    @xxfye 如果流程是点菜、做菜、上菜的话。同步应该是来一个人点菜、做菜、上菜,下一个人,点菜、做菜、上菜。。。。这样
    异步的话,就是同时面对多个人,如果 1 个人在点菜,不会卡在等待他点菜这个地方,而是去做其他已经点好菜的客户的菜,等到点好菜,在做菜,不知道我这个描述对不对,反正厨师觉得 mmp
    ErenJaeger
        39
    ErenJaeger   11 天前
    @RockShake 确实不是多线程,只是我想在执行多个查询的时候,某个查询网络 IO 的时候,可以发起其他查询。减少访问时间
    ElmerZhang
        40
    ElmerZhang   11 天前
    @ErenJaeger #21
    相当于某个 API 的耗时从 40ms 优化到 5ms,在某些高性能场景下,这是非常大的提升了
    关于   ·   帮助文档   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2915 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 10:15 · PVG 18:15 · LAX 03:15 · JFK 06:15
    ♥ Do have faith in what you're doing.