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,来实现自己个性化操作数据库的需求。

  • 相关阅读:
    Python基础语法 第2节课(数据类型转换、运算符、字符串)
    python基础语法 第5节课 ( if 、 for )
    python基础语法 第4节课 (字典 元组 集合)
    Python基础语法 第3节课 (列表)
    A. Peter and Snow Blower 解析(思維、幾何)
    C. Dima and Salad 解析(思維、DP)
    D. Serval and Rooted Tree (樹狀DP)
    C2. Balanced Removals (Harder) (幾何、思維)
    B. Two Fairs 解析(思維、DFS、組合)
    D. Bash and a Tough Math Puzzle 解析(線段樹、數論)
  • 原文地址:https://www.cnblogs.com/remixnameless/p/13322134.html
Copyright © 2011-2022 走看看