PgSQL · 捉虫动态 · 执行大SQL语句提示无效的内存申请大小

背景

我们执行一个大SQL时(长度大于512M),会返回如下错误:

ERROR: invalid memory alloc request size 1073741824

复现

我们首先复现出来这个问题

  1. 创建表

     create table byteatable(id int, obj bytea);
    
  2. 插入512M大对象
     #!/bin/bash
     data='a'
     for ((i=1;i<=29;i++));
     do
     data=$data$data
     done
     echo 'build ok'
     psql -U postgres -d postgres << EOF
     insert into byteatable(id,obj) values (1,"$data");
     EOF
     echo 'OK'
    

执行脚本后就能复现出来了

ERROR: invalid memory alloc request size 1073741824

BUG分析

我们先找到出现这个错误的位置。

源码位置:

void *
MemoryContextAlloc(MemoryContext context, Size size)
{
    void *ret;
    AssertArg(MemoryContextIsValid(context));
    if (!AllocSizeIsValid(size))
      elog(ERROR, "invalid memory alloc request size %zu", size);
    context->isReset = false;
    ret = (*context->methods->alloc) (context, size);
    VALGRIND_MEMPOOL_ALLOC(context, ret, size);
    return ret;
}

#define AllocSizeIsValid(size) ((Size) (size) <= MaxAllocSize)

#define MaxAllocSize ((Size) 0x3fffffff) /* 1 gigabyte - 1 */

这里限制的内存是1G - 1,而我们插入的大SQL需要的内存没有1G,那么为什么还提示非法的申请内存大小呢?通过调试跟踪,我们发现是在词法分析的时刻出错的。主要问题是在词法分析的内存申请机制上,申请的内存肯定不能少于sql的长度,如果当前申请的内存不够用,那么将重新申请当前内存乘以2的内存大小。

int literallen; /* actual current string length */

int literalalloc; /* current allocated buffer size */
//当前申请内存初始化
yyext->literalalloc = 1024;
static void
addlit(char *ytext, int yleng, core_yyscan_t yyscanner)
{
    /* enlarge buffer if needed */
    if ((yyextra->literallen + yleng) >= yyextra->literalalloc)
    {
       do {
           yyextra->literalalloc *= 2;
       } while ((yyextra->literallen + yleng) >= yyextra->literalalloc);
       yyextra->literalbuf = (char *) repalloc(yyextra->literalbuf,
                              yyextra->literalalloc);
     }
     /* append new data */
     memcpy(yyextra->literalbuf + yyextra->literallen, ytext, yleng);
     yyextra->literallen += yleng;
}
static void
addlitchar(unsigned char ychar, core_yyscan_t yyscanner)
{
     /* enlarge buffer if needed */
     if ((yyextra->literallen + 1) >= yyextra->literalalloc)
     {
          yyextra->literalalloc *= 2;
          yyextra->literalbuf = (char *) repalloc(yyextra->literalbuf,
         yyextra->literalalloc);
     }
}

从源码中可以看出,每次申请原申请内存的2倍,即yyextra->literalalloc *= 2; 而最大申请内存限制是

#define MaxAllocSize ((Size) 0x3fffffff) /* 1 gigabyte - 1 */

所以我们在词法分析能申请的最大内存是2^29 = 536870912,如果词法分析SQL语句需要的内存大于536870912,那么申请的内存需要再乘2,就会得到2^30 = 1073741824,超过MaxAllocSize=0x3fffffff= 1073741823。所以会提示错误:

ERROR: invalid memory alloc request size 1073741824

当然不仅仅是插入一个大对象才会引起这个问题,只要是SQL语句长度大于512M都是出现这个错误,我们可以使用select复现:

do language plpgsql $$
declare
  v_text text := 'a';
begin
  for i in 1..29 loop
    v_text:=v_text||v_text;
  end loop;
  execute $_$select '$_$||v_text||$_$'$_$;
  raise notice 'execute a sql large than 512MB success.';
exception when others then
  raise notice 'execute a sql large than 512MB failed.';
end;
$$;

BUG修复

其实申请MaxAllocSize是可行的,通过修改源码实现,每当申请的内存大于MaxAllocSize并且SQL需要的长度小于MaxAllocSize时,我们就申请MaxAllocSize大小的内存。

static void
addlit(char *ytext, int yleng, core_yyscan_t yyscanner)
{
    /* enlarge buffer if needed */
    if ((yyextra->literallen + yleng) >= yyextra->literalalloc)
    {
        do {
                yyextra->literalalloc *= 2;
        } while ((yyextra->literallen + yleng) >= yyextra->literalalloc);
        /* we can not alloc more than MaxAllocSize */
        if (yyextra->literalalloc > MaxAllocSize && (yyextra->literallen + yleng) < MaxAllocSize)
                yyextra->literalalloc = MaxAllocSize;
        yyextra->literalbuf = (char *) repalloc(yyextra->literalbuf,
                                                                                        yyextra->literalalloc);
    }
    /* append new data */
    memcpy(yyextra->literalbuf + yyextra->literallen, ytext, yleng);
    yyextra->literallen += yleng;
}
static void
addlitchar(unsigned char ychar, core_yyscan_t yyscanner)
{
    /* enlarge buffer if needed */
    if ((yyextra->literallen + 1) >= yyextra->literalalloc)
    {
        yyextra->literalalloc *= 2;
        /* we can not alloc more than MaxAllocSize */
        if (yyextra->literalalloc > MaxAllocSize && (yyextra->literallen + 1) < MaxAllocSize)
                yyextra->literalalloc = MaxAllocSize;
        yyextra->literalbuf = (char *) repalloc(yyextra->literalbuf,
                                                                                        yyextra->literalalloc);
    }
    /* append new data */
    yyextra->literalbuf[yyextra->literallen] = ychar;
    yyextra->literallen += 1;
}

修复之后可以正常插入了

$ sh pgtest.sh
build ok
INSERT 0 1
OK
时间: 2024-09-29 22:04:39

PgSQL · 捉虫动态 · 执行大SQL语句提示无效的内存申请大小的相关文章

请问大神在数据库中可以执行的sql语句,为什么通过java类连接数据库执行报错

问题描述 请问大神在数据库中可以执行的sql语句,为什么通过java类连接数据库执行报错 报错信息: java.sql.SQLException: Already closed. 解决方案 这个错误提示并不是SQL语句的错误,而是Java的SQL操作类的连接对象已经关闭了,不能执行SQL而报出的异常. 解决方案二: 连接已经关了,当然出错了 解决方案三: 数据库为open,先open 在进行相应cmd命令执行. 解决方案四: 你的连接关闭了,jdbc去连接数据库啊 解决方案五: 这个问题还不是你

sql 查询语句-在pb里面如何动态的执行一个sql语句

问题描述 在pb里面如何动态的执行一个sql语句 其效果是这样的: 在pb的界面上有省.市.区.街道四个字段 所以省需要管理到市,即在省的窗口里面 定义一个变量 在sql里面查询市时需要做到先把省查询出来,即为动态的实现查询市的结果 解决方案 PB动态SQL语句执行应用动态执行sql语句动态执行SQL语句

sql-Sql存储过程 通过判断执行不同SQL语句

问题描述 Sql存储过程 通过判断执行不同SQL语句 先判断当前时间,如果是0点到08点之间,就执行第一条sql语句,否则就执行第二条sql语句我就知道用case when去判断,但是 then 后面怎么写?难道要用一个变量装SQLy语句?然后分别给他赋值? 比如举个例子:当前时间是早5点,就执行 (select * from A)时间是16点,就执行(select * from B) 新人没有币,求好心人解答一下 ,谢谢 解决方案 类似 if @time = 8begin select * f

java跟踪执行的sql语句示例分享_java

代码: 复制代码 代码如下: package com.lwj.test.proxy; import java.lang.reflect.InvocationHandler;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.sql.Connection;import java.sql.SQLExce

MySql使用show processlist查看正在执行的Sql语句

今天上班例行的查看了下服务器的运行状况,发现服务器特卡,是mysqld这个进程占用CPU到了99%导致的. 比较好奇是那个程序在使用mysql导致cpu这么高的,通过show processlist命令查看了当前正在执行的sql语句,从而定位到了对应的程序,发现代码中有一个死循环在不停的查询导致cpu占用99%,原因找到了问题就好解决了.   这里简单的记录一下processlist的用法: processlist 命令的输出结果显示了有哪些线程在运行,可以帮助识别出有问题的查询语句,两种方式使

注解-如何获取这个代码最终执行的SQL语句?

问题描述 如何获取这个代码最终执行的SQL语句? JAVA @Select("SELECT NAME FROM TABLE T WHERE T.FOCUS_ID=#{focusId} AND T.PROS_ID=#{prosupId}") public Bypass queryBypass(@Param("focusId") Integer focusId,@Param("prosupId") Integer prosupId); 在这种执行SQ

oracle导出sql语句的结果集和保存执行的sql语句(深入分析)_oracle

spool--导出sql语句结果集 SQL>spool c:\testsql.sqlSQL>SELECT * FROM table_name;SQL>spool off; sav--保存执行的sql语句 SQL>SELECT * FROM table_name;SQL>sav c:\test.sql;  

linux/windows平台监控mysql执行的sql语句

linux平台 监控mysql执行的sql语句   为了做好配合开发做性能和功能测试,方便监控正在执行的sql语句, 可以在/etc/mysqld中添加如下:   log =/usr/local/mysql/var21005/mysql.log 就可以使用: tail -f mysql.log  www.111cn.net 来监控了   如果需要监控慢查询可以添加如下内容:   log-slow-queries = /usr/local/mysql/var21005/slowquery.log

0624使用10035事件跟踪无法执行的sql语句

[20160624]使用10035事件跟踪无法执行的sql语句.txt --昨天看一份awr报表,链接如下: http://www.itpub.net/thread-2061952-1-1.html --摘要如下: Top 10 Foreground Events by Total Wait Time Event                              Waits     Total Wait Time (sec)    Wait Avg(ms)  % DB time    W