SQLAlchemy session的autocommit autoflush详解

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,961评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,444评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,009评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,082评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,101评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,271评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,738评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,395评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,539评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,434评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,481评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,160评论 3 317
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,749评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,816评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,038评论 1 256
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,548评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,140评论 2 341

推荐阅读更多精彩内容