首先要区分sqlalchemy的session(会话),Connection(连接),pool(连接池)这三者关系,session 使用连接来操作数据库,一旦任务完成 session 会将数据库 connection 交还给 pool
问题:
假设在这样一个工厂函数中初始化app和db
from flask_sqlalchemy import SQLAlchemy
def create_app(config=None):
...
app = Flask(__name__)
db = SQLAlchemy(app)
...
return app
session用create_scoped_session来创建,SQLAlchemy默认用的是scoped_session(包装了一下session,用于保证session线程安全)
class SQLAlchemy(object):
Query = None
def __init__(self, app=None, use_native_unicode=True, session_options=None,
metadata=None, query_class=BaseQuery, model_class=Model,
engine_options=None):
self.use_native_unicode = use_native_unicode
self.Query = query_class
self.session = self.create_scoped_session(session_options)
self.Model = self.make_declarative_base(model_class, metadata)
self._engine_lock = Lock()
self.app = app
self._engine_options = engine_options or {}
_include_sqlalchemy(self, query_class)
if app is not None:# 因为我们传了app,所以这里直接init
self.init_app(app)
def init_app(self, app):
......
# 初始化时设置flask回掉,在请求结束时清理session,可以看remove定义,会先关闭数据库连接,应该就还回pool了,然后再从ScopedRegistry的registry里删除掉session
@app.teardown_appcontext
def shutdown_session(response_or_exc):
......
self.session.remove()
return response_or_exc
scoped_session给每个子进程实例化了一个sqlalchemy.util._collections.ScopedRegistry,通过ScopedRegistry的registry字典给每个greenlet.greenlet线程保存了一份session,这样每个线程就有自己的session,不会有冲突,自己的线程需要session时,用自身实例作为key从registry里面取,取到就复用,取不到就创建保存
# flask_sqlalchemy/__init__.py
def create_scoped_session(self, options=None):
if options is None:
options = {}
scopefunc = options.pop('scopefunc', _app_ctx_stack.__ident_func__)
options.setdefault('query_cls', self.Query)
return orm.scoped_session(
self.create_session(options), scopefunc=scopefunc
)
# sqlalchemy/orm/scoping.py
class scoped_session(object):
def __init__(self, session_factory, scopefunc=None):
self.session_factory = session_factory
if scopefunc:
self.registry = ScopedRegistry(session_factory, scopefunc)
else:
self.registry = ThreadLocalRegistry(session_factory)
......
# sqlalchemy/util/_collections.py
class ScopedRegistry(object):
def __init__(self, createfunc, scopefunc):
self.createfunc = createfunc
self.scopefunc = scopefunc
self.registry = {}
def __call__(self):
key = self.scopefunc()
try:
return self.registry[key]
except KeyError:
return self.registry.setdefault(key, self.createfunc())
......
如果gunicorn配置worker_class = ‘gevent’,使用协程,和上面使用线程一样,只是进程启动时会默认起一个greenlet.greenlet,这个greenlet.greenlet也会创建一个session,貌似一直没用。请求过来后,gevent生成Greenlet处理请求,使用Greenlet实例作为key去registry取session,没有则新建保存。