关于 SQLAlchemy 的 Mode.query 和 session.query 的区别请教 - V2EX
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
n3hatv2
V2EX    Python

关于 SQLAlchemy 的 Mode.query 和 session.query 的区别请教

  •  
  •   n3hatv2 2018-05-11 17:19:44 +08:00 3543 次点击
    这是一个创建于 2717 天前的主题,其中的信息可能已经有所发展或是发生改变。
    class MfAdmin(Base):
    __tablename__ = 'mf_admin'

    id = Column(Integer, primary_key=True)
    name = Column(String(255, 'utf8mb4_unicode_ci'))
    password = Column(String(255, 'utf8mb4_unicode_ci'))

    针对 MfAdmin,看到 sqlalchemy 有两种 query 方法,
    方法 1:
    创建 session 的代码就不写了
    admin = session.query(MfAdmin).filter(MfAdmin.name == "test1").first()
    方法 2:
    admin = MfAdmin.query.filter(xxx).filter(MfAdmin.name == "test1").first()

    问题 1:
    1.两种查询的区别是什么?什么场景用哪一种?
    2. 针对方法 2 查出来的 admin,修改后再基于 session 提交,是否可靠?
    比如:
    admin = MfAdmin.query.filter(xxx).filter(MfAdmin.name == "test1").first()
    admin.password = "123456"
    session.commit()
    12 条回复    2018-05-12 17:49:49 +08:00
    Philippa
        1
    Philippa  
       2018-05-11 19:29:46 +08:00   3
    这里没有写清楚是 flask-sqlalchemy 还是 sqlalchemy。flask-sqlalchemy 的文档写的是 Model.query,它会返回一个 BaseQuery 的类,继承 sqlalchemy 的 Query 类,实现了一些额外的东西,比如 paginate 的方法。但看看上面方法 2 就知道,Model 和 session 一点关系都没有,这是因为在 app 里面封装了,不然 session.commit()怎么会知道上面那个 Model 做了什么。这也造成,当你只想使用 sqlalchemy 而不是 flask app 时,你会发现用不了,因为没有共享上下文,因此在调试和写测试时耦合度会比较高,那些喜欢自己写代码而不是学习一整套方案的人就不喜欢 flask-sqlalchemy 的,比如我。同时从代码看,sqlalchemy 的方案更加直接,session 可以简单理解成用于建立连接,session 传参去 query 模型,最后还要 add 一下加入事务,commit 提交,虽然代码多一点,但逻辑很清洗。而 class 只是映射 sql 逻辑成一个面向对象的 object,通过操作 object 来操作 sql。

    session 是抽象出来的概念,实质关键是用来处理链接问题。建议用 sqlalcehmy 而不是 flask-sqlalchemy,因为解耦方便,组件化复用也可行,而不是跟 flask 耦合在一起。sqlalchemy 可以用 session_scope,create_engine 自己写个连接方法来处理,虽然入手肯定是没 flask-sqlalchemy 那么方便,但也是一次编写多几行代码,终身使用了。另外 peewee 是没有处理这个问题的,看起来更简单但不懂用埋了坑。peewee 和 flask-sqlalchemy 一样,也是 Model.select 这种形式的,但没有自动关闭连接。因此,对于长期运行的任务,队列等,某些数据库由于太长时间接受到请求就会直接中断,因此 peewee 在这种情况会出现 client 报错的原因,因为 timeout 了。peewee 的 issues 的官方给出解决方案竟然是 conn.close()这种东西,这在涉及 import/复杂业务容易出现单个 instance 里过早关闭的问题,感觉就像一不小心就变野指针,是个 bad design。
    n3hatv2
        2
    n3hatv2  
    OP
       2018-05-11 19:54:07 +08:00   1
    @Philippa 感谢解答,我确实没讲清楚是 flask-sqlalchemy 还是 sqlalchemy,我正式因为在 flask-sqlalchemy 里遇到了进程不安全的问题想改成 sqlalchemy,每个进程独立的 session ;然后在查文档的时候发现 sqlalchemy 也有直接基于对象 query 的写法,比如 https://gist.github.com/podhmo/4345741,不过这个例子能看出对象 query 本质上就是 session.query(),通过“ query = DBSession.query_property()” 做了关联。。。
    Philippa
        3
    Philippa  
       2018-05-11 22:19:32 +08:00
    @n3hatv2 #2 Yeah~我还没遇到过进程不安全的问题!那我想请教一下大概原理,或有链接分享一下吗?是多进程,多线程还是协程下不安全,竞态?还是别的原因?我现在有很多和别人一起写的用到 flask-sqlalchemy 项目是在 docker 集群上跑的,web 服务层有的也用到,新的才是纯 sqlalchemy,想了解一下。
    neoblackcap
        4
    neoblackcap  
       2018-05-11 23:28:44 +08:00   1
    @n3hatv2 SQLalchemy 明明是类似 Hibernate 一样的 Data Mapper 模型,偏要往 Active Record 套,这何苦呢?喜欢用 Active Record 那套换别的不好么?开箱即用。
    至于 session 的问题,进程不安全能描述一下吗?
    lolizeppelin
        5
    lolizeppelin  
       2018-05-12 00:40:19 +08:00 via Android
    进程不安全什么鬼

    多进程 惰性初始化保证没问题

    不知道怎么做的话 抄 openstack 的做法
    n3hatv2
        6
    n3hatv2  
    OP
       2018-05-12 00:59:50 +08:00
    @Philippa @neoblackcap 我的模型有点复杂,是在初始化 flask app 的主进程里,通过 multiprocessing.Process 启了很多子进程,每个子进程会 query 出对象,然后还需要通过动态 pool.map 再传递到子子进程里做并发处理,遇到了数据库的值不符合预期,我担心 flask-sqlalchemy 这种全局 session 在不同进程里 query 的对象会被共享,进而导致互串;没有明确的证据,我对 Python 生态的 orm 也不太熟悉,目前的想法是改用原生的 sqlalchemy (非 flask 扩展),然后在每个子进程里创建 session,进程退出时 close,保证每个进程里的 query 对象是唯一的实例。

    那么,我想请教下频繁 db_session = Session() db_session.close() 这个开销对性能的影响大不大?对于我这种场景正确的姿势是什么?我的担心是否是多余的?
    Philippa
        7
    Philippa  
       2018-05-12 01:42:16 +08:00
    @n3hatv2 pool 子进程没用过耶,pool 线程池就用过,还是 subprocessing ? executor ?所以我猜你有两个担心的地方,一是 multiprocessing 这块可能共享,二是 threading 这边可能会共享。前者我一般用一个 docker 一个 process 所以不熟悉,不过据搜索结果看 multiproccessing.array 专门用于共享内存的,并用 actor 模型处理消息,所以我理解是不会共享的,所以这里我们是一样的,不用管。至于 threading 嘛,是会的,我也很常用 threadin_pool.map 然后通道来传消息,还能共享变量。假如你的 Model 绑定了 session,在使用 Model 时,session 在 commit 的时候会把所有都包含进来……所以每个线程应该单独使用 session 来完成。比如这样:

    ```
    # 用调用函数的方式加括号(),一种明确的信号来建立连接
    with session_scope() as session:
    do_something()
    ```
    不过我觉得更好的方法是用 Queue (线程安全的),集中处理所有的存储步骤。pool.map(worker_lambda, args)来执行任务,在 def worker_lambda 里面处理好后结果 push 到 Queue 里面:queue.push(result),然后做一个 while True 的消费者,从 queue 取出结果,然后 session.add(result)来保存结果,result 包含多个结果,单独塞 X, Y, Z 变量也可以,但是多线程下顺序有可能会乱,所以有多个结果就一起塞。设置 timeout/retry/queue.empty 作为超时 /重试 /中止信号。这样就不用担心这种问题了,虽然每个自己处理也可以,但是我觉得那样会增加许多脑资源消耗,而且更容易出错和更难维护,其他人接手修改也容易出现 bug。不然你可以用协程,最后看看数据库的锁默认设置。最后最好避免全局变量这种东西,以后会搞坏自己。

    flask-sqlalchemy 那个我不大记得,我记得是最后 db.session.commit()就提交了,或者有的人直接 auto_commit。这在多线程里面的确会有问题。如有错,请指出。
    Philippa
        8
    Philippa  
       2018-05-12 01:47:41 +08:00
    性能还好,我对比过 peewee 和 sqlalchemy 的性能都差不多。如果不存在锁或其他等待,速度一般卡在大批量数据插入时比较慢,sqlalchemy 有个 bulk_insert 的方法,不过大量时间会花在构建对象或 append 插入数据里面。但具体执行插入到插入完成这个阶段,到底是数据库是瓶颈,还是网络,还是对象的构建销毁比较耗资源,就没测过了。
    n3hatv2
        9
    n3hatv2  
    OP
       2018-05-12 02:14:30 +08:00
    @Philippa
    假设我有表
    class MfAdmin(Base):
    id =xxx
    ....

    我是在主进程里初始化 flask-sqlalchemy 的 db, db = SQLAlchemy(app) ,然后在主进程里以 MfAdmin.query 方式查出一组 user_list,然后将 user_list 以 pool.map()的方式( multiprocessing.Pool 创建进程池)在子进程里对不同的 user(MfAdmin 的 id 不一样)进行并发处理,这里有几个疑问:
    1. 如果 pool.map ()产生的不同子进程里处理相同 id 的 user,是不是会被共享?从你的回答来看,不会共享。
    2. 如果 pool.map ()产生的不同子进程里,其中一个子进程执行 session.commit() ,其他子进程里修改的对象会不会被 commit ?
    出于以上担心,我现在的做法是,属于同一个 db = SQLAlchemy(app)的主进程以及通过 pool.map 产生的所有子进程都只做读操作,如果要修改,扔到 redis 的 list 里,由独立的 db = SQLAlchemy(app) 进行同步的修改。
    neoblackcap
        10
    neoblackcap  
       2018-05-12 14:07:21 +08:00
    @n3hatv2 你的说法是不会发生问题的,具体你可以模拟一下。scope_session 是用了 thread-local 去实现的。而且进程安全什么鬼?只有线程安全。
    你进程拿到的东西都不是一个东西,大家都有自己的调用栈,有自己的堆,不同进程不通过通讯怎么能影响其他进程?不要说 Python,哪怕你用 C,你能影响不同进程里面的对象?除非你的对象自带通讯同步功能,要不然想都别想。
    然后你说的场景啊,我觉得你现在的做法问题不大了,你创建进程的时候本身就有开销,而且你的进程里面创建 Session 本身就是额外创建连接。如果是子进程没有额外数据库操作,你大可创建一个额外的进程进行 reduce 操作
    lolizeppelin
        11
    lolizeppelin  
       2018-05-12 14:12:31 +08:00 via Android
    Linux 就不要用 multiprocessing

    老老实实学下 fork 标准写法去实现多进程

    一般简单点的用 multiprocessing 还好
    复杂的你不熟悉一遍 multiprocessing 源码也随便用是给自己找坑
    Philippa
        12
    Philippa  
       2018-05-12 17:49:49 +08:00
    @n3hatv2 #9 我觉得你现在的方法很好了,那样做就完全不用考虑原先的问题了,walk round 同时也是一种风格更好的 design。Queue 模块放在 multiprocessing 虽然没问题但共享 Queue 比较麻烦,所以 Redis 更方便,就是多了个 Redis 依赖。

    1.不会
    2.是的,假如 session 是同一个,就会覆盖掉了。因为 Model.attr = 'xxx'时相当于塞进了预备被提交的 box 里面,commit 就全部提交了。假如相同 id,应该是原有 box 已有内容被覆盖了一遍(我猜,源码我就不看了= =)。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2516 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 10:48 PVG 18:48 LAX 03:48 JFK 06:48
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86