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 · 2021-07-21 16:12:06 +08:00 · 4192 次点击
    这是一个创建于 1247 天前的主题,其中的信息可能已经有所发展或是发生改变。

    代码如下:

     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  
       2021-07-21 16:29:32 +08:00
    你这代码逻辑上还是`同步`的, 因为你有 await, 会等待每个 sql 执行完成, 就是说一个 sql 执行完后才会去执行下一步的 sql. 只是你这个 sql 还没执行完的过程中, 线程不会卡在这里,, 而是会去执行其他的异步 task. 等你 await 的 task 返回后再继续执行你这个 task.

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

    我手画的 - - 偷懒没画全 希望我表达清楚了
    https://cdn.jsdelivr.net/gh/chaleaoch/CDN@main/images/1626859549049-1626859549045.png
    joApioVVx4M4X6Rf
        7
    joApioVVx4M4X6Rf  
       2021-07-21 17:27:51 +08:00
    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  
       2021-07-21 17:32:55 +08:00
    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  
       2021-07-21 17:40:40 +08:00
    楼主这种用法没有问题。
    pabupa
        10
    pabupa  
       2021-07-21 17:41:05 +08:00
    pabupa
        11
    pabupa  
       2021-07-21 17:42:02 +08:00
    我觉得应该是驱动的问题,,,
    pabupa
        12
    pabupa  
       2021-07-21 17:42:43 +08:00
    @pabupa 比如你的连接池中的连接可能太小了……
    ErenJaeger
        13
    ErenJaeger  
    OP
       2021-07-21 17:49:06 +08:00
    @chaleaoch 是呀,就是我指的就是 gather 里面的任务并发执行的话,执行时长应该是这里面最长执行任务的时长,而不是所有任务执行时长的累积
    Vegetable
        14
    Vegetable  
       2021-07-21 17:52:23 +08:00
    用法没问题,不过信息还是不够。包括链接池大小和具体时常。甚至说,mysql 的性能是不是瓶颈,都需要考虑。
    ErenJaeger
        15
    ErenJaeger  
    OP
       2021-07-21 17:56:42 +08:00
    @BBCCBB 是的,这两种方式是写法的区别。 我的意思是:
    fuction outside(){
    asyncFucn1()
    asyncFunc2()
    asyncFunc3()
    asycnFunc4()
    ......
    }
    如果 outside 函数等待里面异步函数结束退出的话,执行时长应该是内部异步函数执行时间最长的时长吧。那我同时发起 N 个 sql 查询,整体查询时长应该是 N 个查询 sql 中执行时长最长的时长吧
    ErenJaeger
        16
    ErenJaeger  
    OP
       2021-07-21 17:57:45 +08:00
    @pabupa mix 是 5,max 是 10,按理说应该够了,我调整一下试试
    ErenJaeger
        17
    ErenJaeger  
    OP
       2021-07-21 17:58:30 +08:00
    @v2exblog 试了试,差不多,timeit 测试了下跟同步查询的差不多,就感觉很奇怪
    chaleaoch
        18
    chaleaoch  
       2021-07-21 18:00:01 +08:00
    @ErenJaeger 换成 100 次查询试一下.
    BBCCBB
        19
    BBCCBB  
       2021-07-21 18:09:49 +08:00
    sorry, 理解错了.. 光看了你问题里写的 5 个 sql, 我漏看了下面的 gather, 你要问的是 gather 里的这 5 个 task 吧
    BBCCBB
        20
    BBCCBB  
       2021-07-21 18:10:31 +08:00
    刚好你这个 task 里也是 5 个 sql 的 await.. :(
    ErenJaeger
        21
    ErenJaeger  
    OP
       2021-07-21 18:12:08 +08:00
    @chaleaoch 淦,我调了 1000 次,同步 40 多秒,异步 5 秒多。问题生产环境中一个接口里面不可能会有这么高频次的查询,小频次的查询,同步异步的差距就很不明显
    BBCCBB
        22
    BBCCBB  
       2021-07-21 18:12:54 +08:00
    次数调大点试试. 看起来没啥问题. 可以把代码贴完整点, 包括 loop.run_until_complete()这一块
    BBCCBB
        23
    BBCCBB  
       2021-07-21 18:14:57 +08:00
    一个接口不可能这么高, 但是其他的接口也有网络 io, 和你测一个接口 1000 次概念差不多, 并发上去了, asyncio 性能差异就出来了..

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

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

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

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

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

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

    如果 aws 序列中的任何 Task 或 Future 被取消,则将其视为引发了 CancelledError - 在这种情况下不会取消 gather() 调用。这是为了防止取消一个提交的任务 /未来导致其他任务 /未来被取消。
    ErenJaeger
        25
    ErenJaeger  
    OP
       2021-07-21 18:20:55 +08:00
    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  
       2021-07-21 18:28:32 +08:00
    gather 不是顺序执行, 都 asyncio 了, 只要里面 task 不阻塞, 就是异步执行的.

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