python - 在 Google Cloud SQL (GAE) Python 应用程序中管理数据库连接的好方法是什么?

标签 python mysql google-app-engine webapp2 google-cloud-sql

我刚刚开始学习 Google App Engine,并且正在尝试找出一种好的方法来管理我的数据库与 Google Cloud SQL 实例的连接(如果您还没有使用过 GC-SQL,基本上,它是云中的 MySQL,有一些限制)。

我使用 python (2.7) GAE 环境和 webapp2 框架来处理请求。我知道常见问题解答说建议对每个请求建立一个到数据库的新连接,但我不知道关闭连接的推荐方法是什么。每次我在开发过程中尝试删除表时,GC-SQL 都会挂起并且“show processlist”显示有一堆进程(可能是因为我没有关闭数据库)并且其中一个正在等待锁(可能是试图删除表的过程)。这很烦人,迫使我重新启动 GC-SQL 实例(就像重新启动 mysql-server 服务一样,我想)。偶尔也会出现数据库问题,我认为这与我没有真正关闭数据库连接这一事实有关。

因此,例如,我是否应该在我的 webapp2.Requesthandler 子类实例上有一个析构函数来断开与数据库的连接? GAE 对象有时似乎被缓存,所以这也是需要考虑的事情。我想我可以为每个查询连接/查询/断开连接,但这似乎不是最佳选择。

我知道这是一个模糊的问题,但我希望在这方面玩过的人能给我一些提示。

提前致谢!

更新: 我尝试使用 Shay 的回答作为起点,围绕需要游标的方法实现包装器。我收到 GAE 错误。这是一个特定于此的新问题:What are the connection limits for Google Cloud SQL from App Engine, and how to best reuse DB connections?

最佳答案

这是来自 Getting Started Guide 的 helloworld 示例应用程序的完整示例.它基于来自 Shay Erlichmen 的片段和 JJC ,但这个版本是线程安全的。

你可以这样使用它:

  @with_db_cursor(do_commit = True)
  def get(self, cursor):
        cursor.execute('SELECT guestName, content, entryID FROM entries')

app.yaml

application: helloworld
version: 1
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /.*
  script: helloworld.app

helloworld.py

import cgi
import logging
import os
import threading
import webapp2

from google.appengine.api import rdbms

_INSTANCE_NAME = <name goes here>

def _db_connect():
  return rdbms.connect(instance=_INSTANCE_NAME, database='guestbook')

_mydata = threading.local()

def with_db_cursor(do_commit = False):
  """ Decorator for managing DB connection by wrapping around web calls.

  Stores connections and open cursor count in a threadlocal
  between calls.  Sets a cursor variable in the wrapped function. Optionally
  does a commit.  Closes the cursor when wrapped method returns, and closes
  the DB connection if there are no outstanding cursors.

  If the wrapped method has a keyword argument 'existing_cursor', whose value
  is non-False, this wrapper is bypassed, as it is assumed another cursor is
  already in force because of an alternate call stack.
  """
  def method_wrap(method):
    def wrap(self, *args, **kwargs):
      if kwargs.get('existing_cursor', False):
        # Bypass everything if method called with existing open cursor.
        return method(self, None, *args, **kwargs)

      if not hasattr(_mydata, 'conn') or not _mydata.conn:
        _mydata.conn = _db_connect()
        _mydata.ref = 0
        _mydata.commit = False

      conn = _mydata.conn
      _mydata.ref = _mydata.ref + 1

      try:
        cursor = conn.cursor()
        try:
          result = method(self, cursor, *args, **kwargs)
          if do_commit or _mydata.commit:
            _mydata.commit = False
            conn.commit()
          return result
        finally:
          cursor.close()
      finally:
        _mydata.ref = _mydata.ref - 1
        if _mydata.ref == 0:
          _mydata.conn = None
          logging.info('Closing conn')
          conn.close()
    return wrap
  return method_wrap


class MainPage(webapp2.RequestHandler):
  @with_db_cursor(do_commit = True)
  def get(self, cursor):
        cursor.execute('SELECT guestName, content, entryID FROM entries')
        self.response.out.write("""
          <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
          <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
            <head>
               <title>My Guestbook!</title>
            </head>
            <body>""")
        self.response.out.write("""
              <table style="border: 1px solid black">
                <tbody>
                  <tr>
                    <th width="35%" style="background-color: #CCFFCC; margin: 5px">Name</th>
                    <th style="background-color: #CCFFCC; margin: 5px">Message</th>
                    <th style="background-color: #CCFFCC; margin: 5px">ID</th>
                  </tr>""")
        for row in cursor.fetchall():
          self.response.out.write('<tr><td>')
          self.response.out.write(cgi.escape(row[0]))
          self.response.out.write('</td><td>')
          self.response.out.write(cgi.escape(row[1]))
          self.response.out.write('</td><td>')
          self.response.out.write(row[2])
          self.response.out.write('</td></tr>')

        self.response.out.write("""
          </tbody>
            </table>
              <br /> No more messages!
              <br /><strong>Sign the guestbook!</strong>
              <form action="/sign" method="post">
              <div>First Name: <input type="text" name="fname" style="border: 1px solid black"></div>
              <div>Message: <br /><textarea name="content" rows="3" cols="60"></textarea></div>
              <div><input type="submit" value="Sign Guestbook"></div>
            </form>
          </body>
        </html>""")

class Guestbook(webapp2.RequestHandler):
  @with_db_cursor(do_commit = True)
  def post(self, cursor):
    fname = self.request.get('fname')
    content = self.request.get('content')
    # Note that the only format string supported is %s
    cursor.execute('INSERT INTO entries (guestName, content) VALUES (%s, %s)', (fname, content))

    self.redirect("/")

app = webapp2.WSGIApplication(
    [('/', MainPage),
     ('/sign', Guestbook)],
    debug=True)

关于python - 在 Google Cloud SQL (GAE) Python 应用程序中管理数据库连接的好方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10158805/

相关文章:

python - 向新用户 App Engine 显示新消息

google-app-engine - 如何将数据批量上传到 Google App Engine Datastore?

java - Google Appengine Java 使用哪种 JVM 语言 + Web 框架?

python - 没有预测样本的标签中出现警告 : Precision and F-score are ill-defined and being set to 0. 0。使用 `zero_division` 参数

python - Django 测试模型字段

python - 每个 Cog 的前缀不同?

php - MYSQL 错误 1096 - 未使用表 - CODEIGNITER

mysql - 使用 mysql 数据通过 Elasticsearch 实现搜索

Mysql使用PDO的错误信息

python - 将文本文件读入 pandas 数据框失败