Python Peewee 实战指南:连接池配置与常见错误排查

Ryan
Ryan
发布时间:2026-10-12 | 3人浏览过
原创

本文基于 Python 2.7 环境,深入分享 Peewee ORM 的使用经验与避坑指南。重点解析如何通过 playhouse.db_url 模块配置数据库连接,详解连接池(Connection Pool)的工作机制及显式关闭连接的必要性,避免连接泄漏和超时错误。同时提供处理查询结果、Emoji 支持及常见 MySQL 报错的解决方案,适用于中小型项目的 Model 层开发。

本文案例基于 Python 2.7 环境实现。

以下内容主要总结个人使用 Peewee 的经验及遇到的常见问题,不涉及基础操作教程。如果您尚未使用过 Peewee,建议先阅读官方文档。

内容准确性和覆盖面仍在完善中,若遇到新问题欢迎交流讨论。

一、介绍

Peewee 是一款简单、轻量级的 Python ORM 框架。

  1. 其特点可概括为简单、轻量且富有表现力(expressive)。
  2. 支持 Python 2.6+ 及 3.2+ 版本。
  3. 支持的数据库包括 SQLite、MySQL 和 PostgreSQL。
  4. 提供了一系列实用扩展,位于 playhouse 模块中。

总体而言,Peewee 完全能够满足个人或企业中小型项目的 Model 层需求,易于上手且功能强大。

二、基本使用方法

from peewee import *

db = SqliteDatabase('people.db')
class BaseModel(Model):
  class Meta:
    database = db # This model uses the "people.db" database.

class Person(BaseModel):
  name = CharField()
  birthday = DateField()
  is_relative = BooleanField()  

基本用法建议参考官方文档的 quickstart 部分。

三、推荐使用姿势

下文介绍使用过程中的经验与注意事项,旨在帮助更好地使用 Peewee。

3.1 连接数据库

连接数据库时,推荐使用 playhouse 模块中的 db_url。其 connect 方法可通过传入的 URL 字符串生成数据库连接实例。

3.1.1 connect(url, **connect_params)

通过传入的 url 字符串,创建一个数据库实例。

url 格式示例如下:

  1. mysql://user:passwd@ip:port/my_db 将创建一个本地 MySQL 的 my_db 数据库实例。
  2. mysql+pool://user:passwd@ip:port/my_db?charset=utf8&max_connections=20&stale_timeout=300 将创建一个本地 MySQL 的 my_db 连接池,最大连接数为 20(在多线程应用中,最多打开 max_connections 个连接。每个线程或 gevent greenlet 拥有独立连接),超时时间为 300 秒(将创建 PooledMySQLDatabase 实例)。

注意:charset 默认为 utf8。如需支持 Emoji,请将 charset 设置为 utf8mb4,并确保创建数据库时字符集设置正确 CREATE DATABASE mydatabase CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。。

支持的 schemes 如下:

  1. apsw: APSWDatabase
  2. mysql: MySQLDatabase
  3. mysql+pool: PooledMySQLDatabase
  4. postgres: PostgresqlDatabase
  5. postgres+pool: PooledPostgresqlDatabase
  6. postgresext: PostgresqlExtDatabase
  7. postgresext+pool: PooledPostgresqlExtDatabase
  8. sqlite: SqliteDatabase
  9. sqliteext: SqliteExtDatabase
  10. sqlite+pool: PooledSqliteDatabase
  11. sqliteext+pool: PooledSqliteExtDatabase

3.1.2 推荐姿势

from playhouse.db_url import connect

from dock.common import config

# url: mysql+pool://root:root@127.0.0.1:3306/appmanage?max_connections=300&stale_timeout=300
mysql_config_url = config_dict.get('config').get('mysql').get('url')
db = connect(url=mysql_config_url)

更多详情请参阅官方文档:db-url。

3.2 连接池的使用

使用 Peewee 连接池时,需显式关闭连接。下文先解释原因,最后给出推荐用法以避免陷阱。

3.2.1 为什么要显式的关闭连接

Connections will not be closed exactly when they exceed their stale_timeout. Instead, stale connections are only closed when a new connection is requested.(连接不会在超过 stale_timeout 时立即关闭,仅在新连接请求时才会关闭陈旧连接。)

引用官方文档提示:大致意为“超时连接不会自动关闭,仅在新请求时关闭”。此处的 request 指“Web 框架处理的请求”,Peewee 源码片段如下:

def _connect(self, *args, **kwargs):
  while True:
    try:
      # Remove the oldest connection from the heap.
      ts, conn = heapq.heappop(self._connections) # _connections是连接实例的list(pool)
      key = self.conn_key(conn)
    except IndexError:
      ts = conn = None
      logger.debug('No connection available in pool.')
      break
    else:
      if self._is_closed(key, conn):
        # This connecton was closed, but since it was not stale
        # it got added back to the queue of available conns. We
        # then closed it and marked it as explicitly closed, so
        # it's safe to throw it away now.
        # (Because Database.close() calls Database._close()).
        logger.debug('Connection %s was closed.', key)
        ts = conn = None
        self._closed.discard(key)
      elif self.stale_timeout and self._is_stale(ts):
        # If we are attempting to check out a stale connection,
        # then close it. We don't need to mark it in the "closed"
        # set, because it is not in the list of available conns
        # anymore.
        logger.debug('Connection %s was stale, closing.', key)
        self._close(conn, True)
        self._closed.discard(key)
        ts = conn = None
      else:
        break
  if conn is None:
    if self.max_connections and (
        len(self._in_use) >= self.max_connections):
      raise ValueError('Exceeded maximum connections.')
    conn = super(PooledDatabase, self)._connect(*args, **kwargs)
    ts = time.time()
    key = self.conn_key(conn)
    logger.debug('Created new connection %s.', key)

  self._in_use[key] = ts # 使用中的数据库连接实例dict
  return conn

根据 pool 库中 _connect 方法的代码可知:每次建立数据库连接时,会检查连接实例是否超时。但需注意:使用中的数据库连接实例(位于 _in_use dict 中)在创建新连接时不会被检查是否超时。

因为该代码中,每次创建连接实例都是从 _connections(pool) 中获取,若有则判断是否超时;若无则新建。

然而,使用中的数据库连接并不在 _connections 中,因此每次创建数据库连接实例时,并未检测使用中的连接实例是否超时。

只有调用连接池实例的 _close 方法后,才会将使用后的连接实例放回 _connections (pool)。

def _close(self, conn, close_conn=False):
  key = self.conn_key(conn)
  if close_conn:
    self._closed.add(key)
    super(PooledDatabase, self)._close(conn) # 关闭数据库连接的方法
  elif key in self._in_use:
    ts = self._in_use[key]
    del self._in_use[key]
    if self.stale_timeout and self._is_stale(ts):  # 到这里才会判断_in_use中的连接实例是否超时
      logger.debug('Closing stale connection %s.', key)
      super(PooledDatabase, self)._close(conn)  # 超时的话,关闭数据库连接
    else:
      logger.debug('Returning %s to pool.', key)
      heapq.heappush(self._connections, (ts, conn)) # 没有超时的话,放回到pool中

3.2.2 如果不显式的关闭连接,会出现的问题

若不调用 _close 方法,使用后的数据库连接将永远不会关闭(包含两层含义:不回到 pool 中且不关闭数据库连接),这将导致两个问题:

1. 每次新建数据库连接,因为 pool 中没有可用连接实例。这会导致在有一定并发量时返回 Exceeded maximum connections. 错误。

2. MySQL 也有 timeout 机制,若一个连接长时间无请求,MySQL Server 会关闭该连接。但 Peewee 已建立的连接实例(特指已建立的)不知晓 MySQL Server 已关闭连接,若再通过此连接请求数据,将返回 Error 2006: “MySQL server has gone away 错误。根据官方文档:

3.2.3 推荐姿势

因此,每次操作完数据库后应关闭连接实例。

用法 1:使用 with

def send_rule():
  with db.execution_context():
  # A new connection will be opened or, if using a connection pool,
  # pulled from the pool of available connections. Additionally, a
  # transaction will be started.
    for user in get_all_user():
      user_id = user['id']
      rule = Rule(user_id)
      rule_dict = rule.slack_rule(index)
      .....do something.....

用法 2:使用 Flask hook

@app.before_request
def _db_connect():
  database.connect()
#
# This hook ensures that the connection is closed when we've finished
# processing the request.
@app.teardown_request
def _db_close(exc):
  if not database.is_closed():
    database.close()
#
#
# 更优雅的用法:
from playhouse.flask_utils import FlaskDB
from dock_fastgear.model.base import db
#
app = Flask(__name__)
FlaskDB(app, db) # 这样就自动做了上面的事情(具体实现可查看http://docs.peewee-orm.com/en/latest/peewee/playhouse.html?highlight=Flask%20DB#flask-utils)

更多详情请参阅官方文档:pool-apis。

3.3 处理查询结果

此处无大坑,但有两点需要注意:

首先,查询结果均为该 Model 的 object,而非 dict。若需结果为 dict,需使用 playhouse 模块的工具方法进行转换:from playhouse.shortcuts import model_to_dict。

其次,get 方法仅返回一条记录。

3.3.1 推荐姿势

from playhouse.shortcuts import model_to_dict
from model import HelloGitHub

def read_from_db(input_vol):
  content_list = []
  category_object_list = HelloGitHub.select(HelloGitHub.category).where(HelloGitHub.vol == input_vol)\
    .group_by(HelloGitHub.category).order_by(HelloGitHub.category)

  for fi_category_object in category_object_list:
    hellogithub = HelloGitHub.select()\
      .where((HelloGitHub.vol == input_vol)
          & (HelloGitHub.category == fi_category_object.category))\
      .order_by(HelloGitHub.create_time)
    for fi_hellogithub in hellogithub:
      content_list.append(model_to_dict(fi_hellogithub))
  return content_list

四、常见错误及解决办法

4.1 'buffer' object has no attribute 'translate'

  1. 错误信息:"'buffer' object has no attribute 'translate'"
  2. 场景:BlobField 字段存储 zlib compress 压缩的数据。
  3. 解决办法:需指定 pymysql 版本小于 0.6.7,否则报错。
  4. 参考

4.2 Can't connect to MySQL server Lost connection to MySQL server during query

  1. 错误信息:Can't connect to MySQL server Lost connection to MySQL server during query
  2. 场景:向 RDS 中插入数据。
  3. 解决办法:因请求连接数过多,达到 RDS 设置上限,需调高 RDS 连接数。
  4. 参考

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表