postgresql 在数据库服务器中消耗更多的内存用于长 运行 连接

postgresql consume more memory in db server for long running connection

我们有一个 C++ 服务器应用程序,它使用 libpq 库连接到 postgresql 数据库。应用程序创建 100 秒的数据库连接,连接的大部分生命周期都是应用程序范围。

最初的应用程序 运行 没问题,但是一段时间后 postgres 服务器为 运行 长连接消耗了更多内存。通过编写下面的示例程序,我了解到使用 PQsendPreparePQsendQueryPrepared 创建准备好的语句会导致数据库服务器中的内存消耗问题。

我们如何解决这个服务器内存问题?是否有任何 libpq 函数可以释放服务器中的内存?

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>

int main(int argc, char *argv[]) {

    const int LEN = 10;
    const char *paramValues[1];

    int paramFormats[1];
    int rowId = 7369;
    Oid paramTypes[1];
    char str[LEN];
    snprintf(str, LEN, "%d", rowId);
    paramValues[0] = str;
    paramTypes[0]=20;
    paramFormats[0]=0;
    long int c=1;

    PGresult* result;
    //PGconn *conn = PQconnectdb("user=scott dbname=dame");
    PGconn *conn = PQsetdbLogin ("", "", NULL, NULL, "dame", "scott", "tiger") ;

    if (PQstatus(conn) == CONNECTION_BAD) {
        fprintf(stderr, "Connection to database failed: %s\n",
            PQerrorMessage(conn));
    do_exit(conn);
    }
    char *stm = "SELECT coalesce(ename,'test') from emp where empno=";
    for(;;)
    {
        std::stringstream strStream ; 
        strStream << c++ ;
        std::string strStatementName = "s_" + strStream.str() ;
        if(PQsendPrepare(conn,strStatementName.c_str(), stm,1,paramTypes) )
        {
            result = PQgetResult(conn); 
            if (PQresultStatus(result) != PGRES_COMMAND_OK)
            {
                PQclear(result) ;
                result = NULL ;
                do
                {
                    result = PQgetResult(conn);
                    if(result != NULL)
                    {
                        PQclear (result) ;
                    }
                } while (result != NULL) ;
                std::cout<<"error prepare"<<PQerrorMessage (conn)<<std::endl;
                break;
            }
            PQclear(result) ;
            result = NULL ;
            do
            {
                result = PQgetResult(conn);
                if(result != NULL)
                {
                    PQclear (result) ;
                }
            } while (result != NULL) ;
        }
        else
        {
            std::cout<<"error:"<<PQerrorMessage (conn)<<std::endl;
            break;
        }

        if(!PQsendQueryPrepared(conn,
                strStatementName.c_str(),1,(const char* const *)paramValues,paramFormats,paramFormats,0))
        {
            std::cout<<"error:prepared "<<PQerrorMessage (conn)<<std::endl;
        }
        if (!PQsetSingleRowMode(conn))
        {
            std::cout<<"error singrow mode "<<PQerrorMessage (conn)<<std::endl;
        }
    result = PQgetResult(conn);
        if (result != NULL)
        {
            if((PGRES_FATAL_ERROR == PQresultStatus(result)) || (PGRES_BAD_RESPONSE == PQresultStatus(result)))
            {
                PQclear(result);
                result = NULL ;
                do
                {
                    result = PQgetResult(conn);
                    if(result != NULL)
                    {
                        PQclear (result) ;
                    }
                } while (result != NULL) ;
                break;
            }

            if (PQresultStatus(result) == PGRES_SINGLE_TUPLE)
            {
                std::ofstream myfile;
            myfile.open ("native.txt",std::ofstream::out |     std::ofstream::app);
                myfile << PQgetvalue(result, 0, 0)<<"\n";
                myfile.close();
                PQclear(result);
                result = NULL ;
                do
                {
                    result = PQgetResult(conn) ;
                    if(result != NULL)
                    {
                        PQclear (result) ;
                    }
                }
                while(result != NULL) ;
                sleep(10);
            }
            else if(PQresultStatus(result) == PGRES_TUPLES_OK || PQresultStatus(result) ==  PGRES_COMMAND_OK)
            {
                PQclear(result);
                result = NULL ;
                do
                {
                    result = PQgetResult(conn) ;
                    if(result != NULL)
                    {
                        PQclear (result) ;
                    }
                }
                while(result != NULL) ;
            }
       }

    }

    PQfinish(conn);
    return 0;

}

Initially application was running fine, but over a period of time postgres server consuming more memory for long running connections. By writing a below sample program I come to know creating prepared statements using PQsendPrepare and PQsendQueryPrepared is causing the memory consumption issue in database server.

嗯,这似乎不足为奇。您在外循环的每次迭代中生成一个新的准备语句名称,然后创建并执行该名称的准备语句。只要连接打开,所有生成的 differently-named 准备好的语句确实会保留在服务器的内存中。这是故意的。

How we can fix this server memory issue?

我将其描述为程序逻辑问题,而不是服务器内存问题,至少就测试程序而言是这样。您获取资源(准备好的语句),然后在您不再使用它们时允许它们闲逛。这些语句 本身并没有泄漏 ,因为您可以重新创建 algorithmically-generated 语句名称,但问题类似于资源泄漏。在你的程序中,而不是在 Postgres 中。

如果您想使用 one-off 准备好的语句,请给它们空字符串 "" 作为它们的名称。 Postgres 称这些为 "unnamed" 语句。您准备的每个未命名语句都将替换属于同一连接的任何先前语句。

但即便如此,这也是一种技巧。首先,准备好的语句最重要的特性是它们可以重用。您的测试程序准备的每条语句都是相同的,因此您不仅在浪费内存,还在浪费 CPU 个周期。你应该只准备一次——通过PQsendPrepare(),或者可能只是PQprepare()——当它成功准备好后,用PQsendQueryPrepared()或[=执行任意多次14=],每次都传递相同的语句名称(但可能有不同的参数)。

is there any libpq function to free the memory in server?

The documentation for the synchronous versions of the query functions 说:

Prepared statements for use with PQexecPrepared can also be created by executing SQL PREPARE statements. Also, although there is no libpq function for deleting a prepared statement, the SQL DEALLOCATE statement can be used for that purpose.

据我所知,在 Postgres 中只有一种预准备语句,同步函数和异步函数都使用类似的语句。所以不,libpq 没有提供专门用于删除与连接关联的准备好的语句的功能,但是您可以在 SQL 中编写语句来做这份工作。当然,创建一个新的 uniquely-named 准备语句来执行这样的语句是没有意义的。

大多数程序不需要这么多不同的准备语句来产生您报告的那种问题。