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

  1. 创建表

  2. 插入512M大对象

    1. #!/bin/bash
    2. data='a'
    3. for ((i=1;i<=29;i++));
    4. do
    5. data=$data$data
    6. done
    7. echo 'build ok'
    8. psql -U postgres -d postgres << EOF
    9. insert into byteatable(id,obj) values (1,"$data");
    10. EOF
    11. echo 'OK'
  1. ERROR: invalid memory alloc request size 1073741824

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

源码位置:

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

  1. int literallen; /* actual current string length */
  2. int literalalloc; /* current allocated buffer size */
  3. //当前申请内存初始化
  4. yyext->literalalloc = 1024;
  1. static void
  2. addlit(char *ytext, int yleng, core_yyscan_t yyscanner)
  3. {
  4. /* enlarge buffer if needed */
  5. if ((yyextra->literallen + yleng) >= yyextra->literalalloc)
  6. {
  7. do {
  8. yyextra->literalalloc *= 2;
  9. } while ((yyextra->literallen + yleng) >= yyextra->literalalloc);
  10. yyextra->literalalloc);
  11. }
  12. /* append new data */
  13. yyextra->literallen += yleng;
  14. }
  15. static void
  16. addlitchar(unsigned char ychar, core_yyscan_t yyscanner)
  17. {
  18. /* enlarge buffer if needed */
  19. if ((yyextra->literallen + 1) >= yyextra->literalalloc)
  20. {
  21. yyextra->literalalloc *= 2;
  22. yyextra->literalbuf = (char *) repalloc(yyextra->literalbuf,
  23. yyextra->literalalloc);
  24. }
  25. }
  1. #define MaxAllocSize ((Size) 0x3fffffff) /* 1 gigabyte - 1 */

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

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

  1. do language plpgsql $$
  2. declare
  3. v_text text := 'a';
  4. begin
  5. for i in 1..29 loop
  6. v_text:=v_text||v_text;
  7. end loop;
  8. execute $_$select '$_$||v_text||$_$'$_$;
  9. raise notice 'execute a sql large than 512MB success.';
  10. exception when others then
  11. raise notice 'execute a sql large than 512MB failed.';
  12. end;
  13. $$;

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

  1. static void
  2. {
  3. if ((yyextra->literallen + yleng) >= yyextra->literalalloc)
  4. {
  5. do {
  6. yyextra->literalalloc *= 2;
  7. } while ((yyextra->literallen + yleng) >= yyextra->literalalloc);
  8. /* we can not alloc more than MaxAllocSize */
  9. if (yyextra->literalalloc > MaxAllocSize && (yyextra->literallen + yleng) < MaxAllocSize)
  10. yyextra->literalalloc = MaxAllocSize;
  11. yyextra->literalbuf = (char *) repalloc(yyextra->literalbuf,
  12. yyextra->literalalloc);
  13. }
  14. /* append new data */
  15. memcpy(yyextra->literalbuf + yyextra->literallen, ytext, yleng);
  16. yyextra->literallen += yleng;
  17. }
  1. static void
  2. addlitchar(unsigned char ychar, core_yyscan_t yyscanner)
  3. {
  4. /* enlarge buffer if needed */
  5. if ((yyextra->literallen + 1) >= yyextra->literalalloc)
  6. {
  7. yyextra->literalalloc *= 2;
  8. /* we can not alloc more than MaxAllocSize */
  9. if (yyextra->literalalloc > MaxAllocSize && (yyextra->literallen + 1) < MaxAllocSize)
  10. yyextra->literalalloc = MaxAllocSize;
  11. yyextra->literalbuf = (char *) repalloc(yyextra->literalbuf,
  12. yyextra->literalalloc);
  13. }
  14. /* append new data */
  15. yyextra->literalbuf[yyextra->literallen] = ychar;
  16. }