Я разбираю текстовые документы и вставляю их в базу данных 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
(ссылки).
При повторении этого цикла в течение некоторого времени (например, 2000 или 15000 повторений - это непредсказуемо) он завершается ошибкой SQL - не может вставить нулевой 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 Engine (чем - есть ли способ для обхода)?
Я тестировал выше на 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: Если я исключу польские национальные символы (ąćęłńóśźż
) из сгенерированных слов, ошибки не будет - код выполняет все 50000 итераций. Я проверял это несколько раз. Итак, для этой строки:
private static final String letters = "abcdefghijklmnopqrstuvwxyz";
ошибки нет, все вроде бы нормально, но с этой строкой (или с оригинальной строкой в полном источнике выше):
private static final String letters = "ąćęłńóśźżjklmnopqrstuvwxyz";
Я получаю ошибку, описанную выше.
UPDATE3: Я только что опубликовал аналогичный вопрос без использования Java - полностью перенесен на чистый plpgsql, посмотрите здесь: Почему этот код не работает в PostgreSQL и как его исправить (обходной путь) ? Это недостаток движка Postgres SQL? . Теперь я знаю, что это не связано с Java - это проблема только Postgres.