【问题标题】:Using SQLAlchemy session from Flask raises "SQLite objects created in a thread can only be used in that same thread"使用 Flask 中的 SQLAlchemy 会话会引发“在线程中创建的 SQLite 对象只能在同一线程中使用”
【发布时间】:2016-03-04 17:16:57
【问题描述】:

我有一个 Flask 视图,它使用 SQLAlchemy 来查询和显示一些博客文章。我正在使用 mod_wsgi 运行我的应用程序。此视图在我第一次访问该页面时有效,但下次返回 500 错误。回溯显示错误ProgrammingError: SQLite objects created in a thread can only be used in that same thread. 为什么会出现此错误以及如何解决?

views.py

engine = create_engine('sqlite:////var/www/homepage/blog.db')
Base.metadata.bind = engine
DBSession = sessionmaker(bind = engine)
session = DBSession()

@app.route('/blog')
@app.route('/blog.html')
def blog():
    entrys = session.query(Entry).order_by(desc(Entry.timestamp)).all()
    return render_template('blog.html', blog_entrys = entrys)

models.py:

class Entry(Base):
    __tablename__ = 'entry'

    id = Column(Integer, primary_key = True)

    title = Column(String(100), nullable = False)
    body = Column(String, nullable = False)
    timestamp = Column(DateTime, nullable = False)
    featured = Column(Boolean, nullable = False)

    comments = relationship('Comment')

    def is_featured(self):
        return self.featured


class Comment(Base):
    __tablename__ = 'comment'

    id = Column(Integer, primary_key = True)
    entry_id = Column(Integer, ForeignKey('entry.id'))

    text = Column(String(500), nullable = False)
    name = Column(String(80))


engine = create_engine('sqlite:////var/www/homepage/blog.db')
Base.metadata.create_all(engine)
Exception on /blog.html [GET]
Traceback (most recent call last):
  File "/usr/lib/python2.6/dist-packages/flask/app.py", line 861, in wsgi_app
    rv = self.dispatch_request()
  File "/usr/lib/python2.6/dist-packages/flask/app.py", line 696, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/var/www/homepage/webserver.py", line 38, in blog
    entrys = session.query(Entry).order_by(desc(Entry.timestamp)).all()
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/orm/query.py", line 1453, in all
    return list(self)
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/orm/query.py", line 1565, in __iter__
    return self._execute_and_instances(context)
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/orm/query.py", line 1570, in _execute_and_instances
    mapper=self._mapper_zero_or_none())
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/orm/session.py", line 735, in execute
    clause, params or {})
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/engine/base.py", line 1157, in execute
    params)
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/engine/base.py", line 1235, in _execute_clauseelement
    parameters=params
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/engine/base.py", line 1348, in __create_execution_context
    None, None)
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/engine/base.py", line 1343, in __create_execution_context
    connection=self, **kwargs)
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/engine/default.py", line 381, in __init__
    self.cursor = self.create_cursor()
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/engine/default.py", line 523, in create_cursor
    return self._connection.connection.cursor()
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/pool.py", line 383, in cursor
    c = self.connection.cursor(*args, **kwargs)
ProgrammingError: (ProgrammingError) SQLite objects created in a thread can only be used in that same thread.The object was created in thread id 140244498364160 and this is thread id 140244523542272 None [{}]

【问题讨论】:

    标签: python sqlite flask sqlalchemy


    【解决方案1】:

    this SO answer 得到提示,我搜索了 SA 文档,发现您可以这样做:

    engine = create_engine('sqlite:////var/www/homepage/blog.db?check_same_thread=False')
    

    scoped_session 并不适合我的情况,因为 Flask-SQLAlchemy 只接受连接字符串参数:

    from flask import Flask
    from flask_sqlalchemy import SQLAlchemy
    
    
    class Config(object):
        SQLALCHEMY_DATABASE_URI = 'sqlite:///app.db?check_same_thread=False'
    
    
    db = SQLAlchemy()
    
    
    def create_app():
        app.config.from_object(Config)
        app = Flask(__name__)
        db.init_app(app)
        ...
    

    根据sqlite3.connect

    默认情况下,check_same_threadTrue,只有创建线程可以 使用连接。如果设置False,返回的连接可能是 跨多个线程共享。 当使用多个线程时 用户应将相同的连接写入操作序列化到 避免数据损坏。

    【讨论】:

    • 我更喜欢这个解决方案,因为 Sqlalchemy 建议使用全局会话,但我不明白为什么。
    【解决方案2】:

    如果您跨线程共享会话,SQLAlchemy(在这种情况下也是 SQLite)不起作用。您可能没有明确使用线程,但mod_wsgi 是,并且您已经定义了一个全局session 对象。使用scoped_session 为每个线程创建一个唯一的会话。

    session = scoped_session(sessionmaker(bind=engine))
    
    @app.teardown_request
    def remove_session(ex=None):
        session.remove()
    
    @app.route('/')
    def example():
        item = session.query(MyModel).filter(...).all()
        ...
    

    最好使用Flask-SQLAlchemy,它会为您处理这些事情和其他事情。 SQLAlchemy 文档建议您使用集成库,而不是自己这样做。

    db = SQLAlchemy(app)
    
    @app.route('/')
    def example():
        item = db.session.query(MyModel).filter(...).all()
        ...
    

    还请注意,您应该只定义一次引擎、会话等并将其导入其他地方,而不是像当前代码那样在每个文件中重新定义它。

    【讨论】:

      【解决方案3】:

      我在使用 SQLAlchemy + SQLite + FastAPI 时遇到过类似的问题,我添加了参数
      connect_args={"check_same_thread": False}

      完整代码:

      #SqlAlchemy Setup 
      SQLALCHEMY_DATABASE_URL = 'sqlite+pysqlite:///.db.sqlite3:' engine = create_engine(SQLALCHEMY_DATABASE_URL,
                                 connect_args={"check_same_thread": False},
                                 echo=True,
                                 future=True
                                 ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base()
      

      【讨论】:

        猜你喜欢
        • 2017-03-01
        • 2020-09-12
        • 2018-06-21
        • 2021-09-20
        • 2019-10-21
        • 2021-01-16
        • 2019-10-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多