Я работал над задачей разбора текста, когда обнаружил странное поведение Postgres. Мой оригинальный код, показывающий странную ошибку, был написан на Java с возможностью подключения JDBC для PostgreSQL (проверено v8.3.3 и v8.4.2), вот мой оригинальный пост: Является ли это ошибкой SQL-движка PostgreSQL и как ее избежать (обходной путь) это? . Я только что перенес свой код Java, приведенный там, на чистый plpgsql, и он выдает те же ошибки (то же поведение, что описано в оригинальном посте).
Упрощенный код теперь не имеет ничего общего с разбором - он просто генерирует псевдослучайные (но повторяемые) слова и вставляет их после нормализации (таблица spb_word
содержит уникальные слова и идентификаторы, на которые они ссылаются по идентификатору в итоговой таблице spb_obj_word
и таблица spb_word4obj
работает как буфер ввода).
Вот мои таблицы (c & p из OP):
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)
);
и код, портированный на plpgsql из исходного кода Java:
create sequence spb_wordnum_seq;
create or replace function spb_getWord() returns text as $$
declare
rn int;
letters varchar(255) := 'ąćęłńóśźżjklmnopqrstuvwxyz';
--'abcdefghijklmnopqrstuvwxyz';
llen int := length(letters);
res text := '';
wordnum int;
begin
select nextval('spb_wordnum_seq') into wordnum;
rn := 3 * (wordnum + llen * llen * llen);
rn := (rn + llen) / (rn % llen + 1);
rn := rn % (rn / 2 + 10);
loop
res := res || substring(letters, rn % llen, 1);
rn := floor(rn / llen);
exit when rn = 0;
end loop;
--raise notice 'word for wordnum=% is %', wordnum, res;
return res;
end;
$$ language plpgsql;
create or replace function spb_runme() returns void as $$
begin
perform setval('spb_wordnum_seq', 1, false);
truncate table spb_word4obj, spb_word, spb_obj_word;
for j in 0 .. 50000-1 loop
if j % 100 = 0 then raise notice 'j = %', j; end if;
delete from spb_word4obj where doc_id = j;
for i in 0 .. 20 - 1 loop
insert into spb_word4obj (word, idx, doc_id) values (spb_getWord(), i, j);
end loop;
update spb_word4obj set word_id = w.id from spb_word w
where w.word = spb_word4obj.word and doc_id = j;
insert into spb_word (word)
select distinct word from spb_word4obj
where word_id is null and doc_id = j;
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;
insert into spb_obj_word (word_id, idx, doc_id)
select word_id, idx, doc_id from spb_word4obj where doc_id = j;
end loop;
end;
$$ language plpgsql;
Для запуска просто выполните select spb_runme()
как оператор SQL.
Вот первый пример ошибки:
NOTICE: j = 8200
ERROR: duplicate key value violates unique constraint "spb_word_word_key"
CONTEXT: SQL statement "insert into spb_word (word) select distinct word from spb_word4obj where word_id is null and doc_id = $1 "
PL/pgSQL function "spb_runme" line 18 at SQL statement
и второй:
NOTICE: j = 500
ERROR: null value in column "word_id" violates not-null constraint
CONTEXT: SQL statement "insert into spb_obj_word (word_id, idx, doc_id) select word_id, idx, doc_id from spb_word4obj where doc_id = $1 "
PL/pgSQL function "spb_runme" line 27 at SQL statement
Эти ошибки происходят непредсказуемым образом - каждый раз на разных итерациях (j
) и с разными словами, вызывающими ошибку.
Когда польские национальные символы (ąćęłńóśźż
) удаляются из сгенерированных слов (строка letters varchar(255) := 'ąćęłńóśźżjklmnopqrstuvwxyz';
становится letters varchar(255) := 'abcdefghijklmnopqrstuvwxyz';
), ошибки нет! Моя БД создается с кодировкой UTF-8, поэтому не должно быть проблем с не-ascii-символами, но, очевидно, это очень важно!
Теперь мой вопрос: что не так с моим кодом? Или это что-то серьезное не так с PostgreSQL? Как обойти эту ошибку?
Кстати: если это ошибка в движке PostgreSQL, то как этой БД можно доверять? Стоит ли переходить на одну из бесплатных альтернатив (например, MySQL)?
ОБНОВЛЕНИЕ: дополнительные объяснения (в основном для пони OMG)
Если я удалю ненужное delete
- у меня все еще будут те же ошибки.
Функция spb_getWord()
должна генерировать слова с дубликатами - она симулирует синтаксический анализ текста и делит его на слова - и некоторые слова повторяются - это нормально, а остальная часть моего кода имеет дело с дубликатами. Из-за возможных дубликатов, генерируемых spb_getWord()
, я вставляю слова в буферную таблицу spb_word4obj
, а затем я обновляю word_id
в этой таблице для уже обработанных слов из spb_word
. Так что теперь - если строка в spb_word4obj
имеет word_id
не нуль - тогда это дубликат, поэтому я не буду вставлять это слово в spb_word
. Но - как упомянул OMG Ponies, я получаю ошибку duplicate key value violates unique constraint
, что означает, что мой код, который правильно обрабатывает дубликаты, завершается неудачей. То есть мой код не работает из-за внутренней ошибки Postgres - правильный код каким-то образом плохо выполняется Postgres и дает сбой.
После вставки новых слов (дубликаты распознаны и помечены как не подлежащие вставке) в spb_word
мой код, наконец, вставляет нормализованные слова в spb_obj_word
- заменяя тело слова ссылкой на не дублированную запись в spb_word
, но иногда это снова не дает результатов из-за внутренней ошибки Postgres. Опять же, я думаю, что мой код правильный, но он не работает, потому что есть проблема в самом движке Postgres SQL.
Добавление или удаление польских национальных букв из сгенерированных слов с помощью spb_getWord
только убеждает меня в том, что это странная ошибка Postgres - все уникальные / дублирующие соображения остаются неизменными, но разрешение / запрещение некоторых букв из слов приводит к ошибкам или устраняет их. Так что в моем коде нет ошибки - неправильная обработка дубликатов.
Второе, что убеждает меня в том, что в моем коде нет ошибок, - это непредсказуемый момент обнаружения ошибок. Каждый прогон моего кода выполняет одну и ту же последовательность слов, поэтому он всегда должен ломаться в одном и том же месте с одним и тем же значением, вызывающим ошибку. Но это не так - это совершенно случайный момент, когда он терпит неудачу.