为什么连接池会减慢烧瓶中的 sql 查询速度

问题描述

连接池应该提高 postgres 的吞吐量,或者至少这是每个人在谷歌搜索什么是池及其好处时所说的,但是每当我在烧瓶中尝试连接池时,结果总是比它慢得多在文件的开头打开一个连接和一个游标,并且从不关闭它们。如果我的 web 应用程序不断收到来自用户的请求,为什么我们甚至关闭连接和游标,创建一个连接和游标一次,然后每当我们收到请求时,GET 或 POST 请求是否只使用现有的请求不是更好吗?游标和连接。我在这里错过了什么吗?! 这是每种方法的时间安排,下面是我运行以对每种方法进行基准测试的代码

使用一个数据库连接完成 100000 次查询需要 16.537524700164795 秒,我们在 Flask 应用程序开始时打开了一次,从未关闭

使用 psqycopg2 池化方法完成 100000 次查询需要 38.07477355003357 秒

使用 pgbouncer 池化方法完成 100000 次查询需要 52.307902574539185 秒

这里还有一个运行测试的视频,如果有帮助的话 https://youtu.be/V2kzKApDs8Y

我用来对每种方法进行基准测试的烧瓶应用程序是

import psycopg2
import time
from psycopg2 import pool
from flask import Flask

app = Flask(__name__)

connection_pool = pool.SimpleConnectionPool(1,50,host="localhost",database="test",user="postgres",password="test",port="5432")

connection = psycopg2.connect(host="127.0.0.1",port="5432")
cursor = connection.cursor()

pgbouncerconnection_pool = pool.SimpleConnectionPool(1,host="127.0.0.1",port="6432")

@app.route("/poolingapproach")
def zero():
    start = time.time()
    for x in range(100000):
        with connection_pool.getconn() as connectionp:
            with connectionp.cursor() as cursorp:
                cursorp.execute("SELECT *  from tb1 where id = %s",[x%100])
                result = cursorp.fetchone()
                connection_pool.putconn(connectionp)
    y = "it took " +  str(time.time() - start) + " seconds to finish 100000 queries with the pooling approach"
    return str(y),200

@app.route("/pgbouncerpooling")
def one():
        start = time.time()
        for x in range(100000):
                with pgbouncerconnection_pool.getconn() as pgbouncer_connection:
                        with pgbouncer_connection.cursor() as pgbouncer_cursor:
                                pgbouncer_cursor.execute("SELECT *  from tb1 where id = %s",[x%100])
                                result = pgbouncer_cursor.fetchone()
                                pgbouncerconnection_pool.putconn(pgbouncer_connection)
        a = "it took " +  str(time.time() - start) + " seconds to finish 100000 queries with pgbouncer pooling approach"
        return str(a),200



@app.route("/oneconnection_at_the_begining")
def two():
    start = time.time()
    for x in range(100000):
        cursor.execute("SELECT * from tb1 where id = %s",[x%100])
        result = cursor.fetchone()
    end = time.time()
    x = 'it took ' + str(end - start)+ ' seconds to finish 100000 queries with one database connection that we don\'t close'
    return str(x),200
if  __name__=="__main__":
    app.run()

解决方法

我不确定你是如何测试的,但是连接池的想法基本上是解决两件事:

  1. 您有更多的用户尝试连接到数据库可以直接处理的数据库(例如,您的数据库允许 100 个连接并且有 1000 个用户同时尝试使用它)和
  2. 节省您用于打开和关闭连接的时间,因为它们始终处于打开状态。

所以我相信你的测试必须专注于这两种情况。

如果您的测试只有 1 个用户请求大量查询,则连接池将只是一种开销,因为它会尝试打开您不需要的额外连接。

如果您的测试总是返回相同的数据,您的 DBMS 可能会缓存结果和查询计划,因此会影响结果。事实上,为了扩展事物,您甚至可以从诸如 elasticsearch 之类的二级缓存中受益。

现在,如果要进行实际测试,必须混合读写操作,在其中添加一些随机变量(强制 DB 不缓存结果或查询计划)并尝试增量加载,这样您就可以看到每次添加更多并发客户端执行请求时系统的行为。

并且由于这些客户端还为测试增加了更多 CPU 负载,您还可以考虑在与为您的数据库提供服务的机器不同的机器上运行客户端,以保持结果公平。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...