a3.sqlalchemy-scoped_session

scoped_session主要用于web应用中,解决SQLAlchemy Session生命周期管理问题,防止内存泄漏。它采用registry模式,类似线程局部存储(TLS),确保每个请求拥有独立的Session,请求结束后自动关闭。在Flask中,推荐使用Flask-SQLAlchemy扩展,但在其他框架或非web应用中,使用scoped_session能更好地管理数据库会话。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

scoped_session防止内存泄漏,保护性session的创建。

SQLAlchemy 的 scoped_session 是啥玩意

通常我们用 SQLAlchemy 写数据的时候要创建 Session 对象来维护数据库会话,用完了再关掉。但是听说还有个叫scoped_session的玩意,这是做啥用的?

这东西其实与 web 应用有一些关系。我们在使用 Django 的 ORM 的时候怎么没见到需要创建个 session 呢?因为它已经悄悄帮你实现好了维护 session 的逻辑,自动进行创建和销毁(多么伟大,多么 friendly 啊),而当我们用 Flask 之类裸奔极客模式的 web 框架时就没有这样的好事了,只能自己搞定。

Session 的生命周期

首先我们需要知道一个 sqlalchemy session 的生命周期是怎样的。我们的 web 应用会同时服务多个用户,因此不同的请求要有不同的 session,不然就会翻车。

session 会在一次请求进来的时候创建出来

session = Session()

在整个请求处理过程中被使用

session.add(some_obj)
session.commit()

在请求处理完毕后被关闭销毁。

session.close()

当然上面的代码只是简略的情形,通常还需要包括 try-except 来处理 session 需要 rollback 之类的情况。

scoped_session 与 registry 模式

scoped_session就是用在 web 应用这种处理请求的场景下,协助进行 session 维护的。

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

session_factory = sessionmaker(bind=some_engine)
ScopedSession = scoped_session(session_factory)

通常我们在初始化 web 应用时通过scoped_session函数对原始的Session工厂进行处理,返回出一个ScopedSession工厂,在每个请求来的时候就可以通过这个工厂获得一个 scoped_session 对象。

这实际上实现了一种 registry 模式,它有个中心化的 registry 来保存已经创建的 session,并在你调用ScopedSession工厂的时候,在 registry 里面找找是不是之前已经为你创建过 session 了,如果有,就直接把这个 session 返回给你,如果没有,就创建一个新的 session,并注册到 registry 中以便你下次来要的时候给你。

some_session = ScopedSession()
some_other_session = ScopedSession()
>>> some_session is some_other_session
True

>>> some_other_session.remove()

>>> new_session = ScopedSession()
>>> new_session is some_session
False

调用scoped_session的remove()方法会调用ScopedSession.close()关闭 session,释放连接资源、把数据库 transaction 状态恢复到初始状态等,最后销毁 session 本身,下回再调用ScopedSession的时候,又会重新创建一个 session 出来。

这样,我们在整个请求的任意环节,都可以开开心心地随时通过工厂来获取一个 “新的”session 对象,而只要在请求处理完的时候移除这个 session 就可以了。如果不用它,那么你在每个需要读写数据库的地方,都要小心翼翼地创建个 session 出来,并记得把它们关掉,不然就造成了资源泄漏

Thread-Local Storage

上面说到 scoped_session 类似单例模式,我们看似创建了新的 session,实际上拿到的可能是之前创建出来的 session。但我们 web 应用通常要同时处理多个请求,我的请求有没有可能不小心拿到别人创建的 session 对象呢?

这是一个好问题,然而正直的 SQLAlchemy 不会让不法分子得逞的

尽管 Python 有着神奇的 GIL,没法真正的并行地跑线程,但至少还是有线程的概念的,对于不同的请求进来的时候我们通常会在不同的线程中进行处理。Python 里有个概念叫 thread local storage(TLS),即线程本地存储,它可以作为全局变量一样使用,但这个数据只在这个线程中有效,与其他的线程是隔离的。

import threading
mydata = threading.local()
mydata.x = 1

等等,全局变量?这不正好和刚才 registry 模式的思想差不多嘛。和我们所希望的一样,scoped_session 也确实使用了 tls 作为 session 的存储方式,一个线程只能拿到自己创建出来的 session,保证了不同线程不会乱入别人的 session。

使用 tls 还有另外一个好处,由于 session 是跟着线程走的,就算你没有调用remove()亲手干掉 session,也会由于线程结束,session 也跟着被一起回收掉,不至于泄漏。(但仍建议在必要的时候对资源进行显式的回收)

还有一个隐蔽的问题,如果我们用了 gevent 来处理并发而不是用多线程,会翻车吗?答案是不会。++gevent 在monkey.patch_all()的时候,已经悄悄把这个 threading 相关的东西悄悄替换成自己的一套了++,thread-local 的东西已经变成了 greenlet-local,不同协程间仍是隔离的,一般不会有问题

要不要用 scoped_session

对于 Flask 框架,强烈不建议自己维护 session,就算我们已经有了 scoped_session,但这玩意仍旧不是那么好用,有很多细节需要处理。Flask 有个名为 Flask-SQLAlchemy 的扩展,它已经把 scoped_session 的这一套在内部帮你配置好了,你只需要正常使用它的db.session,无需关心 session 是怎么来的,又是怎么没的

++但如果用了个别的框架,而它又没有好用的自带 ORM(即除了 Django 之外其他框架),或者是在非 web 应用里使用,这时就应该使用 SQLALchemy 的 scoped_session,来减少一些 bug 或者内存泄漏的可能++

参考:

Contextual/Thread-local Sessions – SQLAlchemy Documentation

threading — Thread-based parallelism – Python Documentation

<think>嗯,用户现在问的是如何设置SQLAlchemy的engine,使用config.py中的配置。他们提供了几个配置变量,比如数据库名、用户、密码,看起来是想动态地从环境变量或者默认值中获取这些配置。 首先,我需要确认用户使用的框架是否是Flask还是纯SQLAlchemy,因为配置方式可能不同。不过从提供的代码来看,用户可能是在使用Flask-SQLAlchemy,因为配置项有SQLALCHEMY_前缀,这在Flask中比较常见。但也不排除他们自己设置的配置。 用户提到的config.py中的变量包括数据库名、用户、密码,但没有提到主机和端口,所以可能需要提醒用户检查这些配置是否存在,或者是否使用默认值。比如,如果数据库在本地,可能主机是localhost,端口是3306。 接下来,用户希望engine使用这些配置。在SQLAlchemy中,创建engine通常需要连接字符串,格式为:dialect+driver://username:password@host:port/database。所以需要将这些配置项组合成这样的字符串。 用户给出的配置中,数据库名有两种情况,被注释掉了,可能需要确认他们使用的是哪一个,或者是否有其他逻辑。但根据代码,当前使用的是e_trainning_test或者环境变量中的值。用户可能需要将这三个配置项(数据库名、用户、密码)结合到连接字符串中,同时还要考虑主机和端口是否在config.py中设置,或者是否有默认值。 另外,用户可能没有提到主机和端口,所以可能需要假设默认是本地主机和默认端口,或者在config.py中是否有其他变量,比如SQLALCHEMY_DATABASE_HOST和SQLALCHEMY_DATABASE_PORT。如果不存在,可能需要用户补充这些配置,或者在连接字符串中使用默认值。 然后,需要指导用户如何从config.py导入这些配置,并构建连接字符串。例如,从config导入SQLALCHEMY_DATABASE_NAME, SQLALCHEMY_DATABASE_USER等,然后拼接到连接字符串中。 还需要注意,如果密码中有特殊字符,需要进行URL编码。比如,如果密码包含@或者:,需要使用urllib.parse.quote_plus来处理。 此外,用户可能关心的是如何在不同的环境中使用环境变量,比如开发、测试、生产环境,使用不同的数据库配置。这时候,使用环境变量和默认值的配置方式是合理的,可以灵活切换环境而不需要修改代码。 最后,可能需要提供一个完整的示例,展示如何导入配置,创建engine,并进行测试连接是否成功。同时提醒用户确保数据库服务正在运行,并且用户有权限访问指定的数据库。 可能还需要提到使用create_engine时的可选参数,比如echo=True来显示SQL语句,方便调试。 总结来说,步骤应该是: 1. 确保config.py中的配置正确,包括主机、端口等信息。 2. 在代码中导入这些配置。 3. 构建连接字符串。 4. 创建engine实例。 5. 测试连接是否成功。</think>以下是基于 SQLAlchemy 配置 MySQL 数据库连接的完整实现方法: --- ### 一、核心步骤 #### 1. **配置 `config.py` 补充完整参数** ```python # config.py import os # 数据库名(优先从环境变量读取,否则使用默认值) SQLALCHEMY_DATABASE_NAME = os.getenv(&#39;SQLALCHEMY_DATABASE_NAME&#39;) or "e_trainning_test" # 数据库用户(优先从环境变量读取,否则使用默认值) SQLALCHEMY_DATABASE_USER = os.getenv(&#39;SQLALCHEMY_DATABASE_USER&#39;) or "root" # 数据库密码(优先从环境变量读取,否则使用默认值) SQLALCHEMY_DATABASE_PASSWORD = os.getenv(&#39;SQLALCHEMY_DATABASE_PASSWORD&#39;) or "root" # 补充其他必要参数(若未在环境变量中设置则使用默认值) SQLALCHEMY_DATABASE_HOST = os.getenv(&#39;SQLALCHEMY_DATABASE_HOST&#39;) or "localhost" SQLALCHEMY_DATABASE_PORT = os.getenv(&#39;SQLALCHEMY_DATABASE_PORT&#39;) or "3306" ``` #### 2. **构建数据库连接 URL** ```python from sqlalchemy import create_engine from config import ( SQLALCHEMY_DATABASE_USER, SQLALCHEMY_DATABASE_PASSWORD, SQLALCHEMY_DATABASE_HOST, SQLALCHEMY_DATABASE_PORT, SQLALCHEMY_DATABASE_NAME ) # 格式化 MySQL 连接字符串 DATABASE_URL = f"mysql+pymysql://{SQLALCHEMY_DATABASE_USER}:{SQLALCHEMY_DATABASE_PASSWORD}@{SQLALCHEMY_DATABASE_HOST}:{SQLALCHEMY_DATABASE_PORT}/{SQLALCHEMY_DATABASE_NAME}" ``` #### 3. **创建 SQLAlchemy Engine** ```python # 创建引擎(echo=True 可开启SQL语句日志,调试用) engine = create_engine( DATABASE_URL, echo=True, # 可选:显示执行的SQL语句 pool_size=5, # 可选:连接池大小 pool_recycle=3600 # 可选:连接自动回收时间(秒) ) ``` --- ### 二、完整代码示例 ```python # database.py from sqlalchemy import create_engine from config import ( SQLALCHEMY_DATABASE_USER, SQLALCHEMY_DATABASE_PASSWORD, SQLALCHEMY_DATABASE_HOST, SQLALCHEMY_DATABASE_PORT, SQLALCHEMY_DATABASE_NAME ) def get_engine(): """创建并返回数据库引擎""" # 构建连接URL(含密码特殊字符处理) from urllib.parse import quote_plus encoded_password = quote_plus(SQLALCHEMY_DATABASE_PASSWORD) # 处理密码中的特殊符号 return create_engine( f"mysql+pymysql://{SQLALCHEMY_DATABASE_USER}:{encoded_password}@" f"{SQLALCHEMY_DATABASE_HOST}:{SQLALCHEMY_DATABASE_PORT}/" f"{SQLALCHEMY_DATABASE_NAME}", echo=False ) # 初始化引擎 engine = get_engine() ``` --- ### 三、关键注意事项 #### 1. **依赖安装** ```bash # 必须安装 MySQL 驱动和 SQLAlchemy pip install pymysql sqlalchemy ``` #### 2. **密码特殊字符处理** 若密码包含 `@`、`:` 等符号,需使用 `urllib.parse.quote_plus()` 编码: ```python from urllib.parse import quote_plus safe_password = quote_plus("your@pass:word") ``` #### 3. **验证连接** ```python # 测试连接是否成功 try: with engine.connect() as conn: print("✅ 数据库连接成功!") except Exception as e: print(f"❌ 连接失败: {str(e)}") ``` #### 4. **环境变量优先级** - 实际部署时,通过 `.env` 文件或服务器环境变量覆盖默认值 **示例 `.env`**: ```env SQLALCHEMY_DATABASE_NAME=e-training_prod SQLALCHEMY_DATABASE_USER=admin SQLALCHEMY_DATABASE_PASSWORD=Secure@Pass123 ``` --- ### 四、扩展:与 Flask-SQLAlchemy 集成 若使用 Flask,可在初始化时直接注入配置: ```python from flask import Flask from flask_sqlalchemy import SQLAlchemy from config import SQLALCHEMY_DATABASE_URI # 需在config.py中定义完整URI app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI db = SQLAlchemy(app) ``` --- 通过以上步骤,即可将数据库配置从 `config.py` 安全地传递到 SQLAlchemy 引擎。实际部署时建议通过环境变量管理敏感信息(如密码)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值