它是 PostgreSQL SQL 引擎的错误吗?如何避免(解决方法)它?

Posted

技术标签:

【中文标题】它是 PostgreSQL SQL 引擎的错误吗?如何避免(解决方法)它?【英文标题】:Is it an error of PostgreSQL SQL engine and how to avoid (workaround) it? 【发布时间】:2010-01-18 14:28:57 【问题描述】:

我正在解析文本文档并将它们插入 PostgreSQL 数据库。我的代码是用 Java 编写的,我使用 JDBC 进行数据库连接。我在向数据库添加数据时遇到了非常奇怪的错误 - 似乎在不可预测的时刻(主循环的迭代次数不同)Postgres 看不到刚刚添加到表中的行并且无法正确执行更新。

也许我做错了什么,所以也许有办法纠正我的代码?还是 PostgreSQL 的严重错误,我应该在 PostgreSQL 主页上发布(作为错误报告)?

以下是我在做什么以及出了什么问题的详细信息。我已经简化了代码以隔离错误 - 简化版本不解析任何文本,但我使用生成的单词对其进行模拟。 源文件包含在我的问题末尾(java 和 sql)。

在我的问题的简化示例中,我有一个线程代码、一个 JDBC 连接、3 个表和很少的 SQL 语句(完整的 Java 源代码少于 90 行)。

主循环适用于“文档” - 20 个单词和后续 doc_id(整数)。

    缓冲区表spb_word4obj 已被清除,以便刚刚插入 doc_id。 字被插入缓冲表(spb_word4obj), 然后将唯一的新词插入到表spb_word中 最后 - 文档的单词被插入到 spb_obj_word - 用来自 spb_word 的单词 ID 替换单词主体(参考)。

在迭代此循环一段时间(例如 2,000 或 15,000 次迭代 - 这是不可预测的)时,它会因 SQL 错误而失败 - 无法将 null word_id 插入spb_word。它变得更加奇怪,因为手动重复最后一次迭代没有错误。似乎 PostgreSQL 在记录插入和语句执行速度方面存在一些问题 - 它会丢失一些数据或在稍有延迟后使其对后续语句可见。

生成的单词序列是可重复的 - 每次运行代码都会生成相同的单词序列,但每次代码失败时的迭代次数都不同。

这是我创建表的sql代码:

create sequence spb_word_seq;

create table spb_word (
  id bigint not null primary key default nextval('spb_word_seq'),
  word varchar(410) not null unique
);

create sequence spb_obj_word_seq;

create table spb_obj_word (
  id int not null primary key default nextval('spb_obj_word_seq'),
  doc_id int not null,
  idx int not null,
  word_id bigint not null references spb_word (id),
  constraint spb_ak_obj_word unique (doc_id, word_id, idx)
);

create sequence spb_word4obj_seq;

create table spb_word4obj (
  id int not null primary key default nextval('spb_word4obj_seq'),
  doc_id int not null,
  idx int not null,
  word varchar(410) not null,
  word_id bigint null references spb_word (id),
  constraint spb_ak_word4obj unique (doc_id, word_id, idx),
  constraint spb_ak_word4obj2 unique (doc_id, word, idx)
);

这里是 Java 代码 - 它可能只是被执行(它有静态 main 方法)。

package WildWezyrIsAstonished;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

public class StrangePostgresBehavior 

    private static final String letters = "abcdefghijklmnopqrstuvwxyząćęłńóśźż";
    private static final int llen = letters.length();
    private Connection conn;
    private Statement st;
    private int wordNum = 0;

    public void runMe() throws Exception 
        Class.forName("org.postgresql.Driver");

        conn = DriverManager.getConnection("jdbc:postgresql://localhost:5433/spb",
                "wwspb", "*****");
        conn.setAutoCommit(true);
        st = conn.createStatement();

        st.executeUpdate("truncate table spb_word4obj, spb_word, spb_obj_word");

        for (int j = 0; j < 50000; j++) 

            try 
                if (j % 100 == 0) 
                    System.out.println("j == " + j);
                

                StringBuilder sb = new StringBuilder();

                for (int i = 0; i < 20; i++) 
                    sb.append("insert into spb_word4obj (word, idx, doc_id) values ('"
                            + getWord() + "'," + i + "," + j + ");\n");
                
                st.executeUpdate("delete from spb_word4obj where doc_id = " + j);

                st.executeUpdate(sb.toString());

                st.executeUpdate("update spb_word4obj set word_id = w.id "
                        + "from spb_word w "
                        + "where w.word = spb_word4obj.word and doc_id = " + j);

                st.executeUpdate("insert into spb_word (word) "
                        + "select distinct word from spb_word4obj "
                        + "where word_id is null and doc_id = " + j);

                st.executeUpdate("update spb_word4obj set word_id = w.id "
                        + "from spb_word w "
                        + "where w.word = spb_word4obj.word and "
                        + "word_id is null and doc_id = " + j);

                st.executeUpdate("insert into spb_obj_word (word_id, idx, doc_id) "
                        + "select word_id, idx, doc_id from spb_word4obj "
                        + "where doc_id = " + j);
             catch (Exception ex) 
                System.out.println("error for j == " + j);
                throw ex;
            
        
    

    private String getWord() 
        int rn = 3 * (++wordNum + llen * llen * llen);
        rn = (rn + llen) / (rn % llen + 1);
        rn = rn % (rn / 2 + 10);

        StringBuilder sb = new StringBuilder();
        while (true) 
            char c = letters.charAt(rn % llen);
            sb.append(c);
            rn /= llen;
            if (rn == 0) 
                break;
            
        

        return sb.toString();
    

    public static void main(String[] args) throws Exception 
        new StrangePostgresBehavior().runMe();
    

再说一遍:是我做错了什么(究竟是什么?)还是 PosgreSQL SQL 引擎中的严重缺陷(比 - 有解决方法)?

我已经在 Windows Vista 上测试过:Java 1.6 / PostgreSQL 8.3.3 和 8.4.2 / JDBC PostgreSQL 驱动程序 postgresql-8.2-505.jdbc3 和 postgresql-8.4-701.jdbc4。所有组合都会导致上述错误。为了确保它不是我在其他机器上的类似环境中测试过的机器。


更新:我已打开 Postgres 日志记录 - 正如 Depesz 所建议的那样。以下是最新执行的 sql 语句:

2010-01-18 16:18:51 CETLOG:  execute <unnamed>: delete from spb_word4obj where doc_id = 1453
2010-01-18 16:18:51 CETLOG:  execute <unnamed>: insert into spb_word4obj (word, idx, doc_id) values ('ouc',0,1453)
2010-01-18 16:18:51 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('rbjb',1,1453)
2010-01-18 16:18:51 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('pvr',2,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('gal',3,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('cai',4,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('żjg',5,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('egf',6,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('śne',7,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('ęęd',8,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('lnd',9,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('cbd',10,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('dąc',11,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('łrc',12,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('zmł',13,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('zxo',14,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('oćj',15,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('zlh',16,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('lńf',17,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('cóe',18,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: 
  insert into spb_word4obj (word, idx, doc_id) values ('uge',19,1453)
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: update spb_word4obj set word_id = w.id from spb_word w where w.word = spb_word4obj.word and doc_id = 1453
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: insert into spb_word (word) select distinct word from spb_word4obj where word_id is null and doc_id = 1453
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: update spb_word4obj set word_id = w.id from spb_word w where w.word = spb_word4obj.word and word_id is null and doc_id = 1453
2010-01-18 16:18:52 CETLOG:  execute <unnamed>: insert into spb_obj_word (word_id, idx, doc_id) select word_id, idx, doc_id from spb_word4obj where doc_id = 1453
2010-01-18 16:18:52 CETERROR:  null value in column "word_id" violates not-null constraint
2010-01-18 16:18:52 CETSTATEMENT:  insert into spb_obj_word (word_id, idx, doc_id) select word_id, idx, doc_id from spb_word4obj where doc_id = 1453

现在 - 代码检查表 spb_word4obj 中的错误:

select * 
from spb_word4obj w4o left join spb_word w on w4o.word = w.word
where w4o.word_id is null

它显示两个词:'gal', 'zxo' 导致了问题。但是...它们可以在spb_word 表中找到 - 刚刚插入日志中的 sql 语句(包括在上面)。

所以 - 这不是 JDBC 驱动程序的问题,而是 Postgres 本身?


UPDATE2:如果我从生成的单词中消除波兰国家字符 (ąćęłńóśźż),则不会出现错误 - 代码执行所有 50,000 次迭代。我已经测试过几次了。所以,对于这一行:

    private static final String letters = "abcdefghijklmnopqrstuvwxyz";

没有错误,一切似乎都很好,但是有了这一行(或上面完整源代码中的原始行):

    private static final String letters = "ąćęłńóśźżjklmnopqrstuvwxyz";

我收到上述错误。


UPDATE3:我刚刚发布了类似的问题,但没有使用 Java - 完全移植到纯 plpgsql,请看这里:Why this code fails in PostgreSQL and how to fix it (work-around)? Is it Postgres SQL engine flaw?。现在我知道它与 Java 无关 - 这是 Postgres 本身的问题。

【问题讨论】:

【参考方案1】:

我对该问题的进一步调查发现该问题与纯 Postgres SQL 有关,我开发了纯 plpgsql 版本,它是上面代码的一对一端口。纯 plpgsql 的重述问题在这里:Why this code fails in PostgreSQL and how to fix it (work-around)? Is it Postgres SQL engine flaw?。

所以 - 这不是 Java/JDBC 相关的问题。

此外,我还设法简化了测试代码 - 现在它使用一个表。简化的问题发布在 pgsql-bugs 邮件列表:http://archives.postgresql.org/pgsql-bugs/2010-01/msg00182.php。确认发生在其他机器上(不仅是我的)。

这里是解决方法:将数据库排序规则从波兰语更改为标准“C”。使用“C”排序规则没有错误。但是如果没有波兰语排序规则,波兰语单词的排序不正确(就波兰语国家字符而言),所以应该在 Postgres 本身中解决问题。

【讨论】:

很高兴看到您找到了答案。在这种情况下,请接受您自己的答案以标记已回答的问题。【参考方案2】:

在 postgresql.conf 中开启查询日志 (log_statement = all),并检查查询。

我敢打赌,这是驱动程序(JDBC)的问题。

【讨论】:

我已经做到了。它似乎不是 JDBC 驱动程序的问题,而是 Postgres 本身的问题。我已经更新了我的问题,以提供导致错误的最后日志条目和表行的详细信息。 这里是纯sql版本的问题:***.com/questions/2089772/…【参考方案3】:

如果您尝试使用 Postgres 索引自然语言文档(据我所知,您正在尝试在文档的单词上构建倒排索引),我建议您改为查看 Full text search in Postgres。


如果这不是一个选项,请检查您的编码设置:

数据库(在 Postgres 中,您实际上无法更改数据库编码 - 您必须从头开始重新创建数据库。) JDBC 驱动程序/Postgres 客户端设置(抱歉记不住细节), 和您的 Java 源代码(编辑器)

我建议将它们全部设置为 UTF-8。

如果这仍然没有帮助,那么我怀疑数据源(您的 Java 源代码文件)和数据目标(数据库)之间存在某种转义/编码问题。

【讨论】:

这不是选择正确的全文搜索方式或类似方法的问题。我在执行原始任务时发现了非常奇怪的错误,并分离了非常简单的示例代码以暴露此错误。我的问题中显示的代码现在与 FTS、解析等无关。这只是为了证明在 PostgreSQL 中执行最简单的语句(插入/更新)时存在严重问题。我所有的编码都设置为 UTF-8,所有值都正确存储在 DB 中——我已经检查过了。所以我的编码不是问题,因为它设置正确。 这里是纯sql版本的问题:***.com/questions/2089772/… 我明白 FTS 在技术上与您的问题无关;我只是建议它作为一种可能的解决方法,因为您要求解决方法。 @Bandi-T:我正在寻找提供代码的解决方法 - 如何更改它以使其正常工作。恕我直言,这只是 PostgreSQL 中严重缺陷的一个例子。没有修复 - 我必须使用我的应用程序逃跑并移至其他数据库(但我希望有解决方法或将修复 Postgres)。 @WildWezyr:好的,在这种情况下,我建议您使用更简洁、更直接的测试用例来更新问题,其中包含针对 Postgres 发出错误所需的实际 SQL 查询。然后我们将能够清楚地确定是 Postgres 有问题,还是发出的查询有问题。然后我们可以向后工作,看看你必须解决什么来解决你的问题。因为我必须承认我发现您的代码太复杂而无法真正深入了解。

以上是关于它是 PostgreSQL SQL 引擎的错误吗?如何避免(解决方法)它?的主要内容,如果未能解决你的问题,请参考以下文章

与 postgres 和套接字“/var/run/postgresql/.s.PGSQL.5432”相关的引擎场错误

如果在sql postgresql中存在,如何使用

Java Applets - 今天它是一个错误的选择吗?

有人用 PostgRest 和 Postgresql 成功实现了 SQL 用户管理吗?

在 PostgreSQL 中删除名称为空的表

我可以通过在PostgreSQL函数中包装SQL来防止SQL注入攻击吗?