flask_sqlalchemy是如何在多线程中对数据库操作不相互影响

数据库操作隔离

结论:使用scoped_session实现数据库操作隔离

flask的api.route()接收一个请求,就会创建一个新的线程去处理,请求之间不相互影响

flask_sqlalchemy是如何使用db.session使多个请求中保函的改变同一个表的sql操作不相互影响的

在flask_sqlalchemy.SQLAlchemy类中关于session的定义:

# Which stack should we use?  _app_ctx_stack is new in 0.9

connection_stack = _app_ctx_stack or _request_ctx_stack

    def __init__(self, app=None,
use_native_unicode=True,
session_options=None):
session_options.setdefault(
'scopefunc', connection_stack.__ident_func__
)
self.session = self.create_scoped_session(session_options) def create_scoped_session(self, options=None):
"""Helper factory method that creates a scoped session."""
if options is None:
options = {}
scopefunc=options.pop('scopefunc', None)
return orm.scoped_session(
partial(_SignallingSession, self, **options), scopefunc=scopefunc
)

self.session = self.create_scoped_session(session_options) 以及最后返回的

return orm.scoped_session(self.create_session(options), scopefunc=scopefunc)

可以看到使用的是sqlalchemy.orm.scoped_session

线程安全:scoped_session
结论:scoped_session会为每一个请求创建独立的session, 由线程id或者_app_ctx_stack.__ident_func__为标记

sqlalchemy的session对象

from sqlalchemy.orm import sessionmaker
session = sessionmaker()

一般我们会通过sessionmaker()这个工厂函数创建session,但这个session并不能用在多线程中,为了支持多线程
操作,sqlalchemy提供了scoped_session,通过名字反映出scoped_session是通过某个作用域实现的
所以在多线程中一般都是如下使用session

from sqlalchemy.orm import scoped_session, sessionmaker
session = scoped_session(sessionmaker())

我们来看看scoped_session是如何提供多线程环境支持的

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)

__init__中,session_factory是创建session的工厂函数,而sessionmaker就是一工厂函数(其实是定义了__call__的

函数)而scopefunc就是能产生某个作用域的函数,如果不提供将使用ThreadLocalRegistry

class ThreadLocalRegistry(ScopedRegistry):
def __init__(self, createfunc):
self.createfunc = createfunc
self.registry = threading.local() def __call__(self):
try:
return self.registry.value
except AttributeError:
val = self.registry.v

从上面__call__可以看出,每次都会创建新的session,并发在线程本地变量中,你可能会好奇__call__是在哪里调用的?

def instrument(name):
def do(self, *args, **kwargs):
return getattr(self.registry(), name)(*args, **kwargs)
return do  
for meth in Session.public_methods:
    setattr(scoped_session, meth, instrument(meth))

正如我们所看到的,当我们调用session.query将会调用 getattr(self.registry(), 'query'),self.registry()就是
调用__call__的时机,但是在flask_sqlalchemy中并没有使用ThreadLocalRegistry,创建scoped_session过程如下

# Which stack should we use?  _app_ctx_stack is new in 0.9
connection_stack = _app_ctx_stack or _request_ctx_stack def __init__(self, app=None,
use_native_unicode=True,
session_options=None):
session_options.setdefault(
'scopefunc', connection_stack.__ident_func__
)
self.session = self.create_scoped_session(session_options) def create_scoped_session(self, options=None):
"""Helper factory method that creates a scoped session."""
if options is None:
options = {}
scopefunc=options.pop('scopefunc', None)
return orm.scoped_session(
partial(_SignallingSession, self, **options), scopefunc=scopefunc
)

我们看到scopefunc被设置为connection_stack.__ident_func__,而connection_stack就是flask中app上下文,
__ident_func__其实就是在多线程中就是thrading.get_ident,也就是线程id
我们看看ScopedRegistry是如何通过_操作的

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())

代码也很简单,其实也就是根据线程id创建对应的session对象,到这里我们基本已经了解了flask_sqlalchemy的线程安全原理。
1.flask_sqlalchemy能否使用ThreadLocalRegistry?
    大部分情况都是可以的,但如果wsgi对多并发使用的是greenlet的模式就不适用了
2.上面create_scoped_session中partial是干嘛的?
    前面我们说过scoped_session的session_factory是可调用对象,但_SignallingSession类并没有定义__call__,所以通过partial支持

这里说一下对db.relationship lazy的理解,看如下代码

class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
users = db.relationship('User', backref='role', lazy='dynamic') class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

假设role是已经获取的一个Role的实例
lazy:dynamic => role.users不会返回User的列表, 返回的是sqlalchemy.orm.dynamic.AppenderBaseQuery对象
                当执行role.users.all()是才会真正执行sql,这样的好处就是可以继续过滤

lazy:select => role.users直接返回User实例的列表,也就是直接执行sql

注意:db.session.commit只有在对象有变化时才会真的执行update

两个比较重要的配置
app.config['SQLALCHEMY_ECHO'] = True =》配置输出sql语句
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True =》每次request自动提交db.session.commit()
这是通过app.teardown_appcontext注册实现

# 绑定app然后初始化sql配置
if app is not None:
self.init_app(app) # 使用钩子,当请求结束后若没有配置自动提交,则移除此session
@teardown
        def shutdown_session(response_or_exc):
            if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']:
                if response_or_exc is None:
                    self.session.commit()
            self.session.remove()
            return response_or_exc # sqlalchemy.orm.scoping.scoped_session
# sqlalchemy.util._collections.ScopedRegistry 定义
def clear(self):
#Clear the current scope, if any.
try:
del self.registry[self.scopefunc()]
except KeyError:
pass

response_or_exc为异常值,默认为sys.exc_info()[1]
上面self.session.remove()表示每次请求后都会销毁self.session,不然会导致存放session的字段太大。


https://blog.csdn.net/luffyser/article/details/89380186

flask_sqlalchemy的session线程安全源码解读的更多相关文章

  1. 线程本地变量ThreadLocal源码解读

      一.ThreadLocal基础知识 原始线程现状: 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步.但是Spring中的各种模板 ...

  2. 从koa-session源码解读session本质

    前言 Session,又称为"会话控制",存储特定用户会话所需的属性及配置信息.存于服务器,在整个用户会话中一直存在. 然而: session 到底是什么? session 是存在 ...

  3. 线程池ThreadPoolExecutor源码解读研究(JDK1.8)

    一.什么是线程池 为什么要使用线程池?在多线程并发开发中,线程的数量较多,且每个线程执行一定的时间后就结束了,下一个线程任务到来还需要重新创建线程,这样线程数量特别庞大的时候,频繁的创建线程和销毁线程 ...

  4. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

  5. AFNetworking 3.0 源码解读 总结(干货)(下)

    承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...

  6. Alamofire源码解读系列(一)之概述和使用

    尽管Alamofire的github文档已经做了很详细的说明,我还是想重新梳理一遍它的各种用法,以及这些方法的一些设计思想 前言 因为之前写过一个AFNetworking的源码解读,所以就已经比较了解 ...

  7. Alamofire源码解读系列(六)之Task代理(TaskDelegate)

    本篇介绍Task代理(TaskDelegate.swift) 前言 我相信可能有80%的同学使用AFNetworking或者Alamofire处理网络事件,并且这两个框架都提供了丰富的功能,我也相信很 ...

  8. Flask(4)- flask请求上下文源码解读、http聊天室单聊/群聊(基于gevent-websocket)

    一.flask请求上下文源码解读 通过上篇源码分析,我们知道了有请求发来的时候就执行了app(Flask的实例化对象)的__call__方法,而__call__方法返回了app的wsgi_app(en ...

  9. flask的请求上下文源码解读

    一.flask请求上下文源码解读 通过上篇源码分析( ---Flask中的CBV和上下文管理--- ),我们知道了有请求发来的时候就执行了app(Flask的实例化对象)的__call__方法,而__ ...

随机推荐

  1. python中的__init_subclass__是什么?

    什么是__init_subclass__ class Hook: def __init_subclass__(cls, **kwargs): print("__init_subclass__ ...

  2. windows控制台,cmd,命令提示符下的基础操作

    打开dos命令窗口1.win+r-->运行-->cmd 2.摁住shift+鼠标右击 选择 在此处打开命令窗口3.在磁盘某文件夹下,选择标题栏中输入框,输入cmd 回车 windows下常 ...

  3. python常用模块:pickle、shelve、json、xml、configparser

    今日内容主要有: 一.pickle模块二.shelve模块三.json模块四.json练习五.xml模块 六.xml练习七.configparser模块 一.pickle模块 #pickle是一个用来 ...

  4. kotlin函数和函数式表达式

    这次的写法可能有些怪异,但是如果熟悉java8的Lambda表达式的话其实理解起来很顺其自然[参考博客:http://www.cnblogs.com/webor2006/p/7705130.html] ...

  5. MyBatis中<![CDATA[ ]]>的使用

    原文地址:https://www.cnblogs.com/catgatp/p/6403382.html <![CDATA[]]>和转义字符 被<![CDATA[]]>这个标记所 ...

  6. 第二章 Vue快速入门--7 讲解v-cloak、v-text、v-html的基本使用

    7 讲解v-cloak.v-text.v-html的基本使用 <!DOCTYPE html> <html lang="en"> <head> & ...

  7. idea 启动ssm项目

    https://www.cnblogs.com/yeya/p/10320885.html https://www.cnblogs.com/chenlinghong/p/8339555.html

  8. (九)zabbix监控web应用

    1)web应用监控介绍 使用zabbix自带的web场景可以监控url的状态码,响应时间,url的下载速度,非常的棒 思路:定义模板-->创建应用集--->定义web场景--->定义 ...

  9. .Net界面开发必备!DevExpress Blazor UI全新组件助力界面开发

    行业领先的.NET界面控件DevExpress 正式发布了v19.1版本,DevExpress UI for Blazor/ Razor组件附带7个用户界面组件(包括Data Grid和Pivot G ...

  10. Web UI开发推荐!Kendo UI for jQuery自定义小部件——使用MVVM

    Kendo UI for jQuery最新试用版下载 Kendo UI目前最新提供Kendo UI for jQuery.Kendo UI for Angular.Kendo UI Support f ...