Почему этот код не работает в PostgreSQL и как это исправить (обходной путь)?Это недостаток движка Postgres SQL? - PullRequest
1 голос
/ 19 января 2010

Я работал над задачей разбора текста, когда обнаружил странное поведение 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 - все уникальные / дублирующие соображения остаются неизменными, но разрешение / запрещение некоторых букв из слов приводит к ошибкам или устраняет их. Так что в моем коде нет ошибки - неправильная обработка дубликатов.

Второе, что убеждает меня в том, что в моем коде нет ошибок, - это непредсказуемый момент обнаружения ошибок. Каждый прогон моего кода выполняет одну и ту же последовательность слов, поэтому он всегда должен ломаться в одном и том же месте с одним и тем же значением, вызывающим ошибку. Но это не так - это совершенно случайный момент, когда он терпит неудачу.

Ответы [ 2 ]

2 голосов
/ 19 января 2010

ВНИМАНИЕ: j = 8200
ОШИБКА: значение дубликата ключа нарушает уникальное ограничение "spb_word_word_key"
КОНТЕКСТ: оператор SQL "вставить в spb_word (слово) выбрать отдельное слово из spb_word4obj, где word_id равно нулю и doc_id= $ 1 "
Функция PL / pgSQL" spb_runme "строка 18 в операторе SQL

... сообщает вам, что ваш spb_getWord() генерирует значения, которые уже существуют в таблице SPB_WORD,Вам нужно обновить функцию, чтобы проверить, существует ли уже слово перед выходом из функции - если оно есть, сгенерировать заново, пока оно не достигнет того, которое не существует.

Я думаю, что ваше spb_runme() должно напоминать:

create or replace function spb_runme() returns void as $$
DECLARE
  v_word VARCHAR(410);

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;

    for i in 0 .. 20 - 1 loop
      v_word := spb_getWord();
      INSERT INTO spb_word (word) VALUES (v_word);

      INSERT INTO spb_word4obj 
        (word, idx, doc_id, word_id)
        SELECT w.word, i, j, w.id
          FROM SPB_WORD w 
         WHERE w.word = v_word;

    end loop;

    INSERT INTO spb_obj_word (word_id, idx, doc_id) 
    SELECT w4o.word_id, w4o.idx, w4o.doc_id 
      FROM SPB_WORD4OBJ w4o 
     WHERE w40.doc_id = j;

  end loop;
end;

Использование этого параметра позволит вам изменить word_id так, чтобы он не поддерживал значения NULL.При работе с внешними ключами заполняйте таблицу ссылками внешнего ключа сначала - начните с родителя, а затем займите его дочерние элементы.

Другое изменение, которое я сделал, - это сохранение spb_getWord()в переменной (v_word), потому что вызов функции несколько раз означает, что вы будете каждый раз получать разные значения.

Последнее - я удалил оператор delete.Вы уже обрезали таблицу, там нечего удалять.Конечно, ничего не связано со значением j.

1 голос
/ 22 января 2010

Мне удалось упростить тестовый код - теперь он использует одну таблицу. Упрощенная проблема была опубликована в списке рассылки pgsql-bugs: http://archives.postgresql.org/pgsql-bugs/2010-01/msg00182.php. Подтверждено, что это происходит на других машинах (не только на моем).

Вот эта упрощенная версия основной тестовой функции (для нее нужна одна таблица spb_word, последовательности spb_wordnum_seq и spb_word_seq и одна функция spb_getWord, приведенная в моем вопросе).

create or replace function spb_runmeSimple2(cnt int) returns void as $$
declare
  w varchar(410);
  wordId int;
begin
  perform setval('spb_wordnum_seq', 1, false);
  truncate table spb_word cascade;

  for i in 1 .. cnt loop

    if i % 100 = 0 then raise notice 'i = %', i; end if;

    select spb_getWord() into w;
    select id into wordId from spb_word where word = w;
    if wordId is null then 
      insert into spb_word (word) values (w);
    end if;

  end loop;
end;
$$ language plpgsql;

Теперь ошибка возникает (но непредсказуемым образом) при выполнении select spb_runmeSimple2(10000000).

Вот обходной путь: измените сопоставление базы данных с польского на стандартное «C». При сопоставлении 'C' ошибки нет. Но без польской сортировки польские слова сортируются неправильно (по отношению к польским национальным символам), поэтому проблема должна быть решена в самом Postgres.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...