那个网站做logo兼职,视觉asp网站源码,网站建设设计流程步骤,安徽外贸网站建设Sqlalchemyflask-sqlalchemy的session是线程安全的#xff0c;但在多进程环境下#xff0c;要确保派生子进程时#xff0c;父进程不存在任何的数据库连接#xff0c;可以通过调用db.get_engine(appapp).dispose()来手动销毁已经创建的engine#xff0c;然后再派生子进程。…Sqlalchemyflask-sqlalchemy的session是线程安全的但在多进程环境下要确保派生子进程时父进程不存在任何的数据库连接可以通过调用db.get_engine(appapp).dispose()来手动销毁已经创建的engine然后再派生子进程。最近线上的项目总是会报出数据库连接相关的错误比如“Command out of Sync”“Mysql server has gone away”“Lost databse connection”“Package sequence out of order”等等最终解决下来发现以上错误可以分为两种一种是和连接丢失有关的一种是和连接被多个线程(进程)同时使用了有关。我们项目基于flask有多线程的场景也有多进程的场景。orm用的是flask的拓展flask-sqlalchemy。flask-sqlalchemy的使用必须基于flask的app实例也就是说要在app上下文中才能使用flask-sqlalchemy所以在某些离线(非web)场景下我们也用到了原生的Sqlalchemy。原生的Sqlalchemy的使用方式是engine create_engine(db_url)Session sessionmaker(bindengine)session Session()session.query(xxx)首先要创建一个engineengine顾名思义就是和数据库连接的引擎。在实际发起查询前是不会创建任何connection的。创建engine时可以通过指定poolclass参数来指定engine使用的连接池。默认是QueuePool也可以设置为NullPool(不使用连接池)。为了方便理解可以把engine视为管理连接池的对象。sqlalchemy中session和我们平时数据库里说的session是两个不同的概念在平时数据库中session的生命周期从连接上数据库开始到断开和数据库的连接位置。但是sqlalchemy中的session更多的是一种管理连接的对象它从连接池取出一个连接使用连接然后释放连接而自身也跟随着销毁。sqlalchemy中的Connection对象是管理真正数据库连接的对象真正的数据库连接在sqlalchemy中是DBAPI。默认地如果不传入poolclass则使用QueuePool(具有一定数量的连接池)如果不指定pool_recycle参数则默认数据库连接不会刷新。也就是说连接如果不适用则一直不去刷新它。但是问题来了在Mysql中输入“show variables like %timeout%; ” 可以看到有一个waittimeout还有interacttimeout默认值为28800(8小时)这两个值代表着如果8个小时内某个数据库连接都不和mysql联系那么就会断掉这个连接。所以8个小时过去了Mysql把连接断掉了但是sqlalchemy客户端这边却还保持着这个连接。当某个时候该连接从连接池被取出使用时就会抛出“Mysql server has gone away”等连接丢失的信息。解决这个问题的办法很简单只要传入pool_recycle参数即可。特别地在flask-sqlalchemy中不会出现这种问题因为falsk-sqlalchemy拓展自动地帮我们注入了pool_recycle参数默认为7200秒。def apply_driver_hacks(self, app, sa_url, options):This method is called before engine creation and used to injectdriver specific hacks into the options. The options parameter isa dictionary of keyword arguments that will then be used to callthe :func:sqlalchemy.create_engine function.The default implementation provides some saner defaults for thingslike pool sizes for MySQL and sqlite. Also it injects the setting ofSQLALCHEMY_NATIVE_UNICODE.if sa_url.drivername.startswith(mysql):sa_url.query.setdefault(charset, utf8)if sa_url.drivername ! mysqlgaerdbms:options.setdefault(pool_size, 10)options.setdefault(pool_recycle, 7200) # 默认7200秒刷新连接elif sa_url.drivername sqlite:pool_size options.get(pool_size)detected_in_memory Falseif sa_url.database in (None, , :memory:):detected_in_memory Truefrom sqlalchemy.pool import StaticPooloptions[poolclass] StaticPoolif connect_args not in options:options[connect_args] {}options[connect_args][check_same_thread] False# we go to memory and the pool size was explicitly set# to 0 which is fail. Let the user know thatif pool_size 0:raise RuntimeError(SQLite in memory database with an empty queue not possible due to data loss.)# if pool size is None or explicitly set to 0 we assume the# user did not want a queue for this sqlite connection and# hook in the null pool.elif not pool_size:from sqlalchemy.pool import NullPooloptions[poolclass] NullPool# if its not an in memory database we make the path absolute.if not detected_in_memory:sa_url.database os.path.join(app.root_path, sa_url.database)unu app.config[SQLALCHEMY_NATIVE_UNICODE]if unu is None:unu self.use_native_unicodeif not unu:options[use_native_unicode] Falseif app.config[SQLALCHEMY_NATIVE_UNICODE] is not None:warnings.warn(The SQLALCHEMY_NATIVE_UNICODE config option is deprecated and will be removed in v3.0. Use SQLALCHEMY_ENGINE_OPTIONS instead.,DeprecationWarning)if not self.use_native_unicode:warnings.warn(use_native_unicode is deprecated and will be removed in v3.0. Use the engine_options parameter instead.,DeprecationWarning)sessionmaker是Session定制方法我们把engine传入sessionmaker中就可以得到一个session工厂通过工厂来生产真正的session对象。但是这种生产出来的session是线程不安全的sqlalchemy提供了scoped_session来帮助我们生产线程安全的session原理类似于Local就是代理session通过线程的id来找到真正属于本线程的session。flask-sqlalchemy就是使用了scoped_session来保证线程安全具体的代码可以在Sqlalchemy中看到构造session时使用了scoped_session。def create_scoped_session(self, optionsNone):Create a :class:~sqlalchemy.orm.scoping.scoped_sessionon the factory from :meth:create_session.An extra key scopefunc can be set on the options dict tospecify a custom scope function. If its not provided, Flasks appcontext stack identity is used. This will ensure that sessions arecreated and removed with the request/response cycle, and should be finein most cases.:param options: dict of keyword arguments passed to session class increate_sessionif 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), scopefuncscopefunc)def create_session(self, options):Create the session factory used by :meth:create_scoped_session.The factory **must** return an object that SQLAlchemy recognizes as a session,or registering session events may raise an exception.Valid factories include a :class:~sqlalchemy.orm.session.Sessionclass or a :class:~sqlalchemy.orm.session.sessionmaker.The default implementation creates a sessionmaker for :class:SignallingSession.:param options: dict of keyword arguments passed to session classreturn orm.sessionmaker(class_SignallingSession, dbself, **options)多进程和数据库连接多进程环境下要注意和数据库连接相关的操作。说到多进程python里最常用的就是multiprocessing。multiprocessing在windows下和linux的表现有所区别在此只讨论linux下的表现。linux下多进程通过fork()来派生要理解我下面说的必须先弄懂fork()是什么东西。粗略地说每个进程都有自己的一个空间称为进程空间每个进程的进程空间都是独立的进程与进程之间互不干扰。fork()的作用就是将一个进程的进程空间完完全全地copy一份copy出来的就是子进程了所以我们说子进程和父进程有着一模一样的地址空间。地址空间就是进程运行的空间这空间里会有进程已经打开的文件描述符文件描述符会间接地指向进程已经打开的文件。也就是说fork()之后父进程子进程会有相同的文件描述符指向相同的一个文件。为什么因为文件是存在硬盘里的fork()时copy的内存中的进程空间并没有把文件也copy一份。这就导致了父进程子进程同时指向同一个文件他们任意一个都可以对这个文件进行操作。这和本文说的数据库有啥关系顺着这个思路想数据库连接是不是一个TCP连接TCP连接是不是一个socketsocket在linux下是什么就是一个文件。所以说如果父进程在fork()之前打开了数据库连接那么子进程也会拥有这个打开的连接。两个进程同时写一个连接会导致数据混乱所以会出现“Command out of sync”的错误两个进程同时读一个连接会导致一个进程读到了另一个没读到就是“No result”。一个进程关闭了连接另一个进程并不知道它试图去操作连接时就会出现“Lost database connection”的错误。在此讨论的场景是父进程在派生子进程之前父进程拥有已打开的数据库连接。派生出子进程之后子进程也就拥有了相应的连接。如果在fork()之前父进程没有打开数据库连接那么也不用担心这个问题。比如Celery使用的prefork池虽然是多进程模型但是celery在派子进程前时不会打开数据库连接的所以不用担心在celery任务中会出现数据库连接混乱的问题。我做的项目里的多进程的场景之一就是使用tornado来跑web应用在派生多个web应用实例时确保此前创建的数据库连接被销毁。app Flask()db Sqlalchemy()db.init_app(app)......db.get_engine(appapp).dispose() # 先销毁已有的engine确保父进程没有数据库连接......fork() # 派生子进程# 例如tornado.start() # 启动多个web实例进程