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-enabledEngine
, but it hasn’t opened any connections yet. When it’s first used, it retrieves a connection from a pool of connections maintained by theEngine
, 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,因为很多情况下,读请求根本不需要事务。
网友评论