美文网首页
SQLAlchemy session的autocommit au

SQLAlchemy session的autocommit au

作者: 大富帅 | 来源:发表于2019-07-29 18:44 被阅读0次
    v2-35ae7c99c4a63f6924ac1436d3bf4c84_1200x500.jpg

    SQLAlchemy 的 session 是指什么

    顾名思义,session就是会话,对话的意思,它的作用就是跟数据库DB交互的
    我们来看sqlalchemy 的session是如何创建的

    from sqlalchemy.orm import sessionmaker
    Session = sessionmaker(bind=engine)
    # or
    Session = sessionmaker()
    Session.configure(bind=engine)  # once engine is available
    
    # 自定义完Session以后,我们要实例化Session,这里创建了两个session
    session1 = Session()
    session2 = Session()
    
    # 之后的增删改查就是在这个session 对象里面操作了
    ed_user = User(name='ed', fullname='Ed Jones', nickname='edsnickname')
    session1.add(ed_user)
    

    The above Session is associated with our SQLite-enabled Engine, but it hasn’t opened any connections yet. When it’s first used, it retrieves a connection from a pool of connections maintained by the Engine, and holds onto it until we commit all changes and/or close the session object.

    session 特点:

    • session的作用就是真正跟database交互的,而engine 就是告诉session使用什么引擎去数据库,也就是mysql呢还是sqlite。

    • session 和连接(connection) 不等同,session 通过连接和数据库进行通信。创建完session对象,还没真正打开数据库连接,当它第一次使用的时候,session就会从连接池获取一个连接来进行跟DB交互,而这个连接池是Engine来维护的,session之后就会hold住这个连接知道commit 或者 关闭session

    • session 是 Query 的入口,当你想要发起查询的时候,一般用法是:session.Query(Model).filter_by(...).first()

    至于线程池,就是在create_engine的时候确定的

    session的autoflush的作用

    • flush 的意思就是将当前 session 存在的变更发给数据库,换句话说,就是让数据库执行 SQL 语句。
    • commit 的意思是提交一个事务。一个事务里面可能有一条或者多条 SQL 语句
    • SQLAlchemy 在执行 commit 之前,肯定会执行 flush 操作;而在执行 flush 的时候,不一定执行 commit,这个主要视 autocommit 参数而定,后面会详细讲

    flush的作用就是相当于在开了一个终端,然后开启了事务start transaction , 把一堆要执行的sql发送到终端。
    假设我们有一个User表:

    class User(Base):
        __tablename__  = 'user'
    
        id = Column(Integer, primary_key=True)
        name = Column(String(64))
    
    // 创建了一个对象,这时,这个对象几乎没有任何意义,session 不知道它的存在
    >>> user = User(name='hello')
    >>> 
    // session.add 这个对象之后,它被 session 放到它的对象池里面去了,但这时不会发送任何 SQL 语句给数据库,数据库目前仍然不知道它的存在
    >>>  session.add(user)
    >>>  
    // session.Query 执行之前,由于 autoflush 是 True,session 会先执行 session1.flush(),然后再发送查询语句
    // 当 session 进行 flush 操作时,session 会先建立(选)一个和数据库的连接,然后将创建 user 的 SQL 语句发送给数据库
    // 所以,这个查询是能查到 user 的
    >>> user = session.query(User).filter_by(name='cosven').first()
    >>> print user, user.id, user.name
    >>> <__main__.User object at 0x7fce4fb663d0> 10 hello
    

    如果 session 的 autoflush 为 False 的话,session 进行查询之前不会把当前累计的修改发送到数据库,而直接发送查询语句,所以下面这个查询是查不到对象的。

    # autoflush 设置成False
    SessionNoAutoflush = sessionmaker(bind=engine, autoflush=False)
    session3 = SessionNoAutoflush()
    
    >>> user = User(name='hello')
    >>>  session3.add(user)
    >>> user = session3.query(User).filter_by(name='cosven').first()
    >>> print user, user.id, user.name
    None
    Traceback (most recent call last):
      File "session.py", line 41, in <module>
        print u, u.id, u.name
    AttributeError: 'NoneType' object has no attribute 'id'
    

    总结flush autoflush

    • flush 就是把sql发给mysql执行的,相当于在一个终端下,start transaction, 然后把sql在事务里面执行,而此时不管最后事务是否commit,insert的时候已经占用了一个id。 紧接着查询这条记录,也是可以查询到的,因为实在同一个事务内,而且提交了SQL给MYSQL执行。外部再插入数据,就会跳过这个id,看上像突然跳过了一下自增id。这个有很大作用,有时候,一个事务内,多个操作,第二个操作依赖于前一个操作的insert_id,这时候在事务你是可以获取到这个id的(以前一直认为需要第一个操作提交事务后才能获取到),然后事务回滚时,也能完整回滚。 所以说事务内一切都是准备好的,回滚可以完整回滚
    • autoflush开启就是,在同一个事务内,当前操作需要查DB记录的,会把之前操作积累的sql自动发送给mysql执行,那么当前的查询操作就会查到相应的数据。如果autoflush关闭了,就不会把积累的sql发送过去,当前的查询操作也不会查询的到。

    session的autocommit作用

    autocommit的意义就是是否自动提交事务,commit就是用来提交事务的。
    这个值是关闭的,那么项目不会自动提交事务,所以默认每个请求开启了事务,并且要手动提交事务
    如果这个值是开启的,说明每个sql都是自动提交事务,也就是说不会开启事务。mysql里面默认是开启的,说明开启事务需求手动开启,begin 或者start transaction

    autocommit=False(sqlalchemy默认)就会全部请求默认开始事务。
    autocommit=True 这样就不会开启事务了。所以需要手动flush修改,告诉mysql执行什么

    场景 :
    flask-sqlalchemy 默认是关闭autocommit的,所以所有请求都相当于自动开启了事务,哪怕是查询的sql也开了。就是因为这个,项目中出了问题。我的api项目,基本都是查询,没有写入的请求。然而这个项目请求量很大,我有几次项目需要修改表结构alert增加字段,结果一直卡住了,导致服务也请求不了,直接挂了。
    后天排查了,发现是因为我的API项目,每个请求都开了事务,而且不会主动去commit 或者rollback,那么请求结束后,事务依然存在连接中,这时候去alert表,这个操作需要获取锁,但是由于有事务存在,它获取不到锁,导致在waiting锁,而外面的请求也需要锁来开启事务,这样就有了死锁效应了。两边都释放不了。

    根本原因就是因为,就连查询的请求SQL都开了事务,这是根本没必要的。后面我把项目的session的autocommit改成了True, 就不会开启事务了。
    而在没有开事务的情况下,需要写入或者修改记录,则需要调用session.flush()来生效

    场景还原:
    session1 开启事务,并且查询team表,这时的查询只是快照查,不是当前读,理论上不会产生锁的

    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from team;
    +------+------+------+----+----+----+----+----+----+----+----+----+----+------+
    | h_id | g_id | num  | c2 | c3 | c5 | c6 | c7 | c8 | c9 | d1 | d2 | d3 | d4   |
    +------+------+------+----+----+----+----+----+----+----+----+----+----+------+
    |    1 |    2 |   40 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 | NULL |
    |    2 |    4 |   37 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 | NULL |
    |    3 |    4 |   40 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  1 | NULL |
    +------+------+------+----+----+----+----+----+----+----+----+----+----+------+
    3 rows in set (0.00 sec)
    

    session2 , 修改表结构,增加字段,这时候已经被卡住了,

    mysql> alter table team add d5 int;
    
    

    session3 再开一个终端,session3模拟其他用户的请求, 这时再次select * team 快照读也卡住了。之后的请求就是这么卡住的,导致最后的服务崩溃

    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from team where id =2
        -> ;
    
    

    查看processlist;

    mysql> mysql> show processlist;
    +----+------+-----------+------+---------+------+---------------------------------+-----------------------------+
    | Id | User | Host      | db   | Command | Time | State                           | Info                        |
    +----+------+-----------+------+---------+------+---------------------------------+-----------------------------+
    | 18 | root | localhost | test | Query   |  257 | Waiting for table metadata lock | alter table team add d5 int |
    | 19 | root | localhost | test | Sleep   |  263 |                                 | NULL                        |
    | 20 | root | localhost | test | Query   |    0 | starting                        | show processlist            |
    +----+------+-----------+------+---------+------+---------------------------------+-----------------------------+
    3 rows in set (0.00 sec)
    
    

    id=18的线程,也就是alter表那个线程,在等待锁,此时已经锁住了team表,其他的线程也不能获取锁读取team表。

    总结 所以不能随便给项目开启autocommit,因为很多情况下,读请求根本不需要事务。

    https://zhuanlan.zhihu.com/p/48994990

    相关文章

      网友评论

          本文标题:SQLAlchemy session的autocommit au

          本文链接:https://www.haomeiwen.com/subject/nxsdrctx.html