zoukankan      html  css  js  c++  java
  • 【15】Flask 数据库连接池

    01 如何在python中操作数据库?

    在后端开发中免不掉与数据库打交道,无非是使用orm或者原生sql来操作数据库。

    在python中通过原生sql操作数据库,主流就两种。

    • 使用pymysql模块:pymysql支持python2.xpython3.x的版本
    • 使用mysqldb模块:mysqldb仅支持python2.x的版本

    orm的使用以flask和django为例。

    • flask使用的orm是基于SQLAlchemy(SQLAlchemy本就是orm),flask团队并在SQLAlchemy基础之上又封装了一个Flask-SQLchemy并予以应用 。
    • django使用的orm是django自带的orm。

    orm的操作数据库的方式我们已经熟知了,这里我们聊一聊如何在web中使用原生sql操作数据库,以及会出现的问题。

    02 在web中使用原生sql(pymysql)操作数据库?

    2.1 在web中通过原生sql操作数据库会出现的问题。

    示例1:

    把所有的数据库操作全部都放在了视图函数里面。

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route("/")
    def hello():
        import pymysql
        CONN = pymysql.connect(host='127.0.0.1',
                               port=3306,
                               user='root',
                               password='123',
                               database='pooldb',
                               charset='utf8')
    
        cursor = CONN.cursor()
        cursor.execute('select * from tb1')
        result = cursor.fetchall()
        cursor.close()
    
        print(result)
    
        return "Hello World"
    
    if __name__ == '__main__':
        app.run()
    

    会出现的问题

    • 很多个用户并发的来请求,一个用户可以理解为一个线程,每个线程都会跟数据库建立连接,数据库承受不了这种量级的连接数。

    示例2

    为了避免之前每个用户都建立连接,我们把数据库连接放到了全局变量里面,只会建立一次连接,但是依然会出现问题。

    from flask import Flask
    
    app = Flask(__name__)
    import pymysql
    CONN = pymysql.connect(host='127.0.0.1',
                               port=3306,
                               user='root',
                               password='123',
                               database='pooldb',
                               charset='utf8')
    
    @app.route("/")
    def hello():
        cursor = CONN.cursor()
        cursor.execute('select * from tb1')
        result = cursor.fetchall()
        cursor.close()
    
        print(result)
    
        return "Hello World"
    
    if __name__ == '__main__':
        app.run()
    

    会出现的问题:

    • 会出现线程安全问题,比如如果第一个用户拿到了连接给关闭了,而第二个用户正在进行查询,第二个用户查询的时候第一个用户把连接断了,会导致第二个用户出现问题。
    • 假设第一用户查询了一下表1,正准备获取查询的内容,这时第二个人查询了一下表2,由于cursor对象都是同一个,第一个人获取到的查询内容就是表2的内容了,所以也会出现线程安全问题

    示例3

    为了避免之前的线程不安全,在示例2的基础上加上一把线程锁

    from flask import Flask
    import threading
    app = Flask(__name__)
    import pymysql
    CONN = pymysql.connect(host='127.0.0.1',
                               port=3306,
                               user='root',
                               password='123',
                               database='pooldb',
                               charset='utf8')
    
    @app.route("/")
    def hello():
        with threading.Lock():
            cursor = CONN.cursor()
            cursor.execute('select * from tb1')
            result = cursor.fetchall()
            cursor.close()
    
            print(result)
    
        return "Hello World"
    
    if __name__ == '__main__':
        app.run()
    

    会出现的问题

    • 根据代码可以发现,只是在示例2的基础上加了一把线程锁,确实是保证了线程安全,但是所有关于数据库操作的请求变成了串行,无法实现并发了。

    小结:

    • 如果直接连接坐在视图函数中,会导致每个用户都要创建连接,数据库承受不了这种量级的连接数。
    • 如果连接数据库的内容做成全局变量的话,无法保证线程安全。
    • 如果定义全局变量用于连接数据库,并且在线程中操作数据库内容加线程锁头,就会变成串行,无法保证并发

    所以既要控制数据库的连接数,又要保证线程安全,又要保证web的并发,这个时候最终的解决方案是数据库连接池。

    2.1 什么是数据库连接池呢?

    数据库连接池概念:数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个,这项技术能明显提高对数据库操作的性能。

    图解

    通俗的讲就是,假设数据库连接池中有5个连接对象,每个用户简单理解为一个线程,比如现在有6个用户同时来访问,6个线程去数据库连接池里面申请数据库的连接对象。前5个线程每个都申请到了连接对象去操作数据库,每个线程使用完了数据库连接对象会归还给数据库连接池,那么第6个线程会等待前5个线程归还连接对象给连接池,再具体一点是:假设第一个线程使用完了连接对象,那么此时6个线程才会结束等待,从而申请到连接对象,以此类推。

    2.2 Python数据库连接池DBUtiles

    DBUtils 是Python的一个用于实现数据库连接池的模块。

    首先安装一下DBUtils模块。

    pip install DBUtils
    

    DBUtils连接池的两种连接模式:

    模式一:为每个线程创建一个连接,线程即使调用了close方法,也不会关闭,只是把连接重新放到连接池,仅供自己的线程再次使用,当线程终止时,连接会自动关闭。(不推荐使用,因为这样需要自己控制线程数量)

    import pymysql
    from DBUtils.PersistentDB import PersistentDB
    from threading import local
    
    POOL = PersistentDB(
        creator=pymysql,  # 使用链接数据库的模块
        maxusage=None,  # 一个链接最多被重复使用的次数,None表示无限制
        setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
        ping=0, # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
        closeable=False,
        # 如果为False时, conn.close() 实际上被忽略,供下次使用,再线程关闭时,才会自动关闭链接。如果为True时, conn.close()则关闭链接,那么再次调用pool.connection时就会报错,因为已经真的关闭了连接(pool.steady_connection()可以获取一个新的链接)
        threadlocal=None,  # 如果为none,用默认的threading.Loacl对象,否则可以自己封装一个local对象进行替换
        host='127.0.0.1',
        port=3306,
        user='root',
        password='123',
        database='pooldb',
        charset='utf8'
    )
    
    def func():
        conn = POOL.connection(shareable=False)
        cursor = conn.cursor()
        cursor.execute('select * from tb1')
        result = cursor.fetchall()
        cursor.close()
        conn.close()
    
    func()
    

    模式二:创建一批连接到连接池,供所有线程共享使用。

    import time
    import pymysql
    import threading
    from DBUtils.PooledDB import PooledDB, SharedDBConnection
    POOL = PooledDB(
        creator=pymysql,  # 使用链接数据库的模块
        maxconnections=6,  # 连接池允许的最大连接数,0和None表示不限制连接数
        mincached=2,  # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
        maxcached=5,  # 链接池中最多闲置的链接,0和None不限制
        maxshared=3,  # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。
        blocking=True,  # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
        maxusage=None,  # 一个链接最多被重复使用的次数,None表示无限制
        setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
        ping=0,
        # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
        host='127.0.0.1',
        port=3306,
        user='root',
        password='123',
        database='pooldb',
        charset='utf8'
    )
    
    
    def func():
        conn = POOL.connection()
        cursor = conn.cursor()
        cursor.execute('select * from tb1')
        result = cursor.fetchall()
        conn.close()
    
    
    func()
    

    2.3 实际开发小应用案例:

    案例目录:

    - app.py
    - db_helper.py
    

    app.py

    from flask import Flask
    from db_helper import SQLHelper
    
    
    app = Flask(__name__)
    
    @app.route("/")
    def hello():
        result = SQLHelper.fetch_one('select * from t1',[])
        print(result)
        return "Hello World"
    
    if __name__ == '__main__':
        app.run()
    

    db_helper.py

    import pymysql
    from DBUtils.PooledDB import PooledDB
    POOL = PooledDB(
        creator=pymysql,  # 使用链接数据库的模块
        maxconnections=6,  # 连接池允许的最大连接数,0和None表示不限制连接数
        mincached=2,  # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
        maxcached=5,  # 链接池中最多闲置的链接,0和None不限制
        maxshared=3,  # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。
        blocking=True,  # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
        maxusage=None,  # 一个链接最多被重复使用的次数,None表示无限制
        setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
        ping=0,
        # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
        host='127.0.0.1',
        port=3306,
        user='root',
        password='123',
        database='pooldb',
        charset='utf8'
    )
    
    
    class SQLHelper(object):
    
        @staticmethod
        def fetch_one(sql,args):
            conn = POOL.connection()
            cursor = conn.cursor()
            cursor.execute(sql, args)
            result = cursor.fetchone()
            conn.close()
            return result
    
        @staticmethod
        def fetch_all(self,sql,args):
            conn = POOL.connection()
            cursor = conn.cursor()
            cursor.execute(sql, args)
            result = cursor.fetchall()
            conn.close()
            return result
    

    以后在开发的过程中可以基于数据库连接池,基于pymysql,来实现自己个性化操作数据库的需求。

  • 相关阅读:
    Zookeeper安装
    JDK安装(Linux)
    Zookeeper简介
    修改tomcat配置解决定时任务多次重复执行
    解决.net mvc session超时的问题
    C#- JSON的操作
    Android SharedPreferences的理解与使用
    大屏适配:flexible.js的源码及配置
    charles抓包工具,抓手机端https设置
    Sanic二十:Sanic 扩展之sanic-openapi生成接口文档之sanic-openapi支持的数据类型
  • 原文地址:https://www.cnblogs.com/remixnameless/p/13322134.html
Copyright © 2011-2022 走看看