Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/330.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
什么';在Google Cloud SQL(GAE)Python应用程序中管理db连接的好方法是什么?_Python_Mysql_Google App Engine_Webapp2_Google Cloud Sql - Fatal编程技术网

什么';在Google Cloud SQL(GAE)Python应用程序中管理db连接的好方法是什么?

什么';在Google Cloud SQL(GAE)Python应用程序中管理db连接的好方法是什么?,python,mysql,google-app-engine,webapp2,google-cloud-sql,Python,Mysql,Google App Engine,Webapp2,Google Cloud Sql,我正在学习Google App Engine,并试图找到一种好方法来管理我与Google Cloud SQL实例的数据库连接(如果你没有使用GC-SQL,基本上,它是云中的MySQL,有一些限制) 我正在使用python(2.7)GAE环境和webapp2框架来处理请求。我知道FAQ上说,建议在每次请求时与DB建立新连接,但我不知道关闭连接的建议方式。每次我在开发过程中尝试删除表时,GC-SQL都会挂起,“ShowProcessList”显示有一堆进程(可能是因为我没有关闭DB),其中一个正在等

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

我正在使用python(2.7)GAE环境和webapp2框架来处理请求。我知道FAQ上说,建议在每次请求时与DB建立新连接,但我不知道关闭连接的建议方式。每次我在开发过程中尝试删除表时,GC-SQL都会挂起,“ShowProcessList”显示有一堆进程(可能是因为我没有关闭DB),其中一个正在等待锁定(可能是试图删除表的进程)。这很烦人,迫使我重新启动GC-SQL实例(我想,就像重新启动mysql服务器服务一样)。我相信偶尔也会出现数据库故障,这与我没有真正关闭数据库连接有关

例如,我应该在webapp2.Requesthandler子类实例上使用析构函数来断开与DB的连接吗?GAE对象有时被缓存,所以这也是要考虑的事情。我想我可以只为每个查询连接/查询/断开连接,但这似乎不太理想

我知道这是一个模糊的问题,但我希望在这个领域打过球的人能给我一些建议

提前谢谢

更新:
我尝试用Shay的答案作为起点,围绕需要游标的方法实现一个包装器。我犯了一些错误。这里有一个新问题与此相关:

我不熟悉Google Cloud SQL,但你不能使用WSGI中间件来打开和关闭连接吗?

我编写了一个修饰程序来处理SQL连接,请随意使用:)



下面是来自的helloworld示例应用程序的完整示例。它基于和的代码段,但此版本是线程安全的

您可以这样使用它:

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

@with_db(commit=True)
def update_user_phone(self, user, phone):
    self.cur.execute(_SQL_UPDATE_USER_PHONE, (phone, user.id))

    # add or replace existing user to cache
    user.phone = phone
    self._update_user_cache(user)

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)
导入cgi
导入日志记录
导入操作系统
导入线程
导入webapp2
从google.appengine.api导入rdbms
_实例名称=
def_db_connect():
返回rdbms.connect(实例=\实例\名称,数据库='留言簿')
_mydata=threading.local()
带有_db_游标的def(do_commit=False):
“”“Decorator,用于通过包装web调用来管理DB连接。
在threadlocal中存储连接和打开的游标计数
调用之间。在包装函数中设置游标变量。可选
执行提交。当wrapped方法返回时关闭光标,然后关闭
如果没有未完成的游标,则返回DB连接。
如果wrapped方法具有关键字参数“existing_cursor”,则其值
如果为非False,则会绕过此包装,因为假定另一个游标为
由于备用调用堆栈,已生效。
"""
def方法_包装(方法):
def包裹(自身、*args、**kwargs):
如果kwargs.get('existing_cursor',False):
#如果使用现有的打开游标调用方法,则忽略所有内容。
返回方法(self、None、*args、**kwargs)
如果不是hasattr(_mydata,'conn')或不是_mydata.conn:
_mydata.conn=\u db\u connect()
_mydata.ref=0
_mydata.commit=False
conn=\u mydata.conn
_mydata.ref=\u mydata.ref+1
尝试:
游标=连接游标()
尝试:
结果=方法(自身、光标、*args、**kwargs)
如果do_commit或_mydata.commit:
_mydata.commit=False
康涅狄格州提交
返回结果
最后:
cursor.close()
最后:
_mydata.ref=\u mydata.ref-1
如果_mydata.ref==0:
_mydata.conn=None
logging.info('关闭连接')
康涅狄格州关闭
回程包装
返回方法
类主页(webapp2.RequestHandler):
@使用\u db\u游标(do\u commit=True)
def get(自身,光标):
execute('SELECT guestName,content,entryID FROM entries')
self.response.out.write(“”)
我的留言簿!
""")
self.response.out.write(“”)
名称
消息
身份证件
""")
对于cursor.fetchall()中的行:
self.response.out.write(“”)
self.response.out.write(cgi.escape(行[0]))
self.response.out.write(“”)
self.response.out.write(cgi.escape(行[1]))
self.response.out.write(“”)
self.response.out.write(第[2]行)
self.response.out.write(“”)
self.response.out.write(“”)

不要再发消息了!
在留言簿上签名! 名字: 消息:
""") 类留言簿(webapp2.RequestHandler): @使用\u db\u游标(do\u commit=True) def post(自身、光标): fname=self.request.get('fname') content=self.request.get('content') #请注意,支持的唯一格式字符串是%s cursor.execute('INSERT INTO entries(guestName,content)value(%s,%s)'(fname,content)) self.redirect(“/”) app=webapp2.WSGIApplication( [(“/”,主页), (“/签名”,留言簿)], debug=True)
这是我的方法,它考虑了可能的异常情况。我在生产环境中使用此方法,效果良好:


def _create_connection(schema):

    if (os.getenv('SERVER_SOFTWARE') and
        os.getenv('SERVER_SOFTWARE').startswith('Google App Engine/')):
        socket = '/cloudsql/%s' % env.DB_INSTANCE_NAME
        return MySQLdb.connect(unix_socket=socket, user=env.DB_APP_USER,
                               passwd=env.DB_APP_PASS, db=schema)
    else:
        return MySQLdb.connect(host='127.0.0.1', port=3306,
                               user=env.DB_APP_USER, passwd=env.DB_APP_PASS,
                               db=schema)


def with_db(commit=False, schema=env.DB_SCHEMA_NAME):

    def method_wrap(method):
        @functools.wraps(method)
        def wrap(self, *args, **kwds):
            # If needed,a connection pool can be added here.
            connection = _create_connection(schema)

            try:
                cur = connection.cursor()
                self.cur = cur
                self.conn = connection

                result = method(self, *args, **kwds)

                if commit:
                    connection.commit()

            except OperationalError as e:

                logging.error('Operational error.\r\nSQL exception: {},\r\n'
                              'Last Query: {}'.format(e, cur._last_executed))

                if commit and connection.open:
                    connection.rollback()
                raise

            except MySQLError as e:

                try:
                    warns = self.conn.show_warnings()
                    error = self.conn.error()
                except:
                    warns = ""
                    error = ""

                logging.error('Try to rolling back transaction.\r\nSQL exception: {},\r\n'
                              'Last Query: {},\r\nConn warn: {},\r\nError: {}'
                              .format(e, cur._last_executed, warns, error))


                if commit and connection.open:
                    connection.rollback()
                raise

            except Exception as e:
                logging.error('Try to rolling back transaction. Non SQL exception: {0}'.format(e))

                if commit and connection.open:
                    connection.rollback()
                raise

            finally:
                connection.close()

            return result
        return wrap
    return method_wrap
您可以这样使用它:

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

@with_db(commit=True)
def update_user_phone(self, user, phone):
    self.cur.execute(_SQL_UPDATE_USER_PHONE, (phone, user.id))

    # add or replace existing user to cache
    user.phone = phone
    self._update_user_cache(user)

谢谢你,圭多。我很谦卑。不幸的是,我在GAE下使用的WSGI框架webapp2似乎没有包含DB API包装器。我对我的项目太投入了,时间太短,无法将其重构为另一个框架。因此,我无法手动管理DB连接。还有其他提示吗?