2010-01-18 4 views
1

J'ai travaillé sur la tâche d'analyse de texte lorsque j'ai trouvé un comportement Postgres étrange. Mon code original exposant une erreur étrange a été écrit en Java avec la connectivité JDBC pour PostgreSQL (v8.3.3 et v8.4.2 testé), voici mon post original: Is it an error of PostgreSQL SQL engine and how to avoid (workaround) it?. Je viens de porter mon code Java donné à plpgsql pur et il donne les mêmes erreurs (même comportement que décrit dans la publication originale).Pourquoi ce code échoue dans PostgreSQL et comment le réparer (contourner)? Est-ce une faille du moteur Postgres SQL?

Code simplifié n'a plus rien à voir avec l'analyse syntaxique - il génère juste des pseudo-aléatoires (mais répétables) mots et les insère après normalisant (tableau spb_word contient des mots et des ids uniques, ils sont référencés par id dans la table finale spb_obj_word et une table spb_word4obj fonctionne comme tampon d'entrée).

Voici mes tableaux (c & p de 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) 
); 

et le code porté à plpgsql à partir du code Java d'origine:

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; 

Pour exécuter ce simplement exécuter select spb_runme() comme instruction SQL.

Voici premier exemple d'erreur:

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 

et seconde:

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 

Ces erreurs se produisent de façon imprévisible - à chaque fois dans différentes itérations (j) et avec différents mots provoquant une erreur .

Lorsque les caractères nationaux polonais (ąćęłńóśźż) sont éliminés des mots générés (ligne letters varchar(255) := 'ąćęłńóśźżjklmnopqrstuvwxyz'; devient letters varchar(255) := 'abcdefghijklmnopqrstuvwxyz';) il n'y a pas d'erreur! Mon DB est créé avec un encodage UTF-8, donc il ne devrait pas y avoir de problème avec les caractères non-ascii, mais apparemment c'est très important! Maintenant, ma question: quel est le problème avec mon code? Ou est-ce quelque chose de grave avec PostgreSQL? Comment contourner cette erreur?

BTW: S'il s'agit d'une erreur dans le moteur PostgreSQL, alors comment cette base de données peut-elle être fiable? Devrais-je passer à l'une des alternatives gratuites (par exemple MySQL)?


MISE À JOUR:explications supplémentaires (principalement pour OMG Poneys)

Si je retire delete inutile - je toujours les mêmes erreurs.

La fonction spb_getWord() doit générer des mots avec des doublons - elle simule l'analyse du texte et la divise en mots - et certains mots se répètent - c'est normal et le reste de mon code traite des doublons. En raison des doublons possibles générés par spb_getWord() j'insère des mots à la table de tampon spb_word4obj puis je mets à jour word_id dans ce tableau pour les mots déjà traités de spb_word. Alors maintenant - si la ligne dans spb_word4obj a word_id non nul - alors c'est un doublon, donc je ne vais pas insérer ce mot dans spb_word.Mais - comme OMG Ponies mentionné, je reçois erreur duplicate key value violates unique constraint ce qui signifie que mon code qui gère les doublons échoue correctement. C'est à dire. mon code échoue en raison d'une erreur Postgres interne - le code correct est en quelque sorte mal exécuté par Postgres et échoue.

Après avoir inséré de nouveaux mots (les doublons reconnus et marqués de ne pas être inséré) dans spb_word mon code insère enfin mots normalisés dans spb_obj_word - remplacer le corps de texte en référence à l'entrée non dupliquée dans spb_word, mais cela échoue à nouveau, parfois à cause d'une erreur interne Postgres . Encore une fois je pense que mon code est correct mais il échoue car il y a un problème dans le moteur Postgres SQL lui-même. Ajouter ou supprimer polonais lettres nationales des mots générés par spb_getWord seulement m'assure qu'il est étrange Postgres erreur - toutes les considérations uniques/doubles restent les mêmes, mais l'autorisation/interdisant certaines lettres de mots conduit à des erreurs ou les élimine. Ce n'est donc pas le cas d'une erreur dans mon code - une mauvaise manipulation des doublons.

La deuxième chose qui m'assure que ce n'est pas une erreur dans mon code est le moment imprévisible des erreurs qui sont détectées. Chaque exécution de mon code fait la même séquence de mots, donc il devrait toujours se casser au même endroit avec la même valeur causant l'erreur. Mais ce n'est pas - c'est un moment assez aléatoire qu'il échoue.

+1

Vous rencontrez des erreurs de contrainte - rien à redire sur Postgres –

+0

@OMG Poneys: oui Je suis en train de rencontrer des erreurs de contraintes. Mais je ne devrais pas! Lisez attentivement mon code - il ne devrait pas y avoir de telles erreurs. Et pourquoi ces erreurs viennent de manière imprévisible avec le code répétable? Pourquoi l'alphabet est-il important (si je supprime tous les caractères nationaux polonais, il n'y a pas d'erreur)? Pourriez-vous m'expliquer cela? – WildWezyr

+0

Essayez d'expérimenter 'set client-encoding ...', voir http://www.postgresql.org/docs/current/static/multibyte.html. Si vous n'obtenez pas de meilleures réponses ici, apportez-le à la liste de diffusion postgresql (vous êtes sûr d'être aidé là-bas). – ChristopheD

Répondre

1

J'ai réussi à simplifier le code de test - maintenant, il utilise une table. Problème simplifié a été posté sur la liste de diffusion pgsql-bugs: http://archives.postgresql.org/pgsql-bugs/2010-01/msg00182.php. Il est confirmé que cela se produit sur d'autres machines (pas seulement les miennes).

Voici cette version simplifiée de la fonction de test principal (il a besoin d'une table spb_word, des séquences spb_wordnum_seq et spb_word_seq et une fonction spb_getWord donnée dans ma question).

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; 

erreur se produit maintenant (mais de manière imprévisible) lors de l'exécution select spb_runmeSimple2(10000000).

Voici une solution de contournement: modifiez le classement de la base de données du polonais vers le standard "C". Avec le classement 'C' il n'y a pas d'erreur. Mais sans polish collation les mots polonais sont triés incorrectement (par rapport aux caractères nationaux polonais), ainsi le problème devrait être fixé dans Postgres lui-même.

+0

'Flaw':' i' va s'incrémenter même si un mot existe déjà. Plus le nombre de mots que vous voulez générer est grand, plus le compteur augmentera sans générer de mots uniques. 'Solution': Vérifiez que le mot est unique * avant * de quitter la fonction de génération de mot. –

+0

Avec le classement polonais, essayez: 'SELECTION ID dans wordId FROM SPB_WORD WHERE mot comme w;' –

+0

@OMG Ponies: 1) il n'y a pas de défaut avec la variable de contrôle de boucle «i» - il est destiné à incrémenter chaque mot généré (dupliquer ou non) et il incrémente correctement. 2) Substituer l'égalité ('=') avec 'like' aide. Ceci est étrange parce que l'égalité et l'égalité sont équivalentes quand il n'y a pas de pourcentage char ('%') impliqué. Mais cette solution de contournement n'aidera pas lorsque des mots sont générés avec percent char. BTW: savez-vous pourquoi l'utilisation de 'like 'aide - quelle différence cela fait-il d'important et élimine les erreurs? – WildWezyr

2

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

... vous dit que votre spb_getWord() est de générer des valeurs qui existent déjà dans le tableau SPB_WORD. Vous devez mettre à jour la fonction pour vérifier si le mot existe déjà avant de quitter la fonction. Si c'est le cas, recréez-la jusqu'à ce qu'elle atteigne celle qui ne l'est pas.

Je pense que vos besoins spb_runme() pour ressembler à:

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; 

En utilisant cela vous permettra de changer les word_id de ne pas soutenir NULLs. Lorsque vous manipulez des clés étrangères, remplissez le tableau des références de clé étrangère d'abord - commencez par le parent, puis attaquez ses enfants.

L'autre changement que j'ai fait était de stocker le spb_getWord() dans une variable (v_word), car appeler la fonction plusieurs fois signifie que vous auriez une valeur différente à chaque fois.

Dernière chose - j'ai supprimé l'instruction delete. Vous avez déjà tronqué la table, il n'y a rien à supprimer. Certainement rien associé à une valeur de j.

+0

s'il vous plaît regardez à la mise à jour à ma question - Je donne des explications pourquoi ce n'est pas un problème avec les doublons dans 'spb_getWord()' mais plutôt une erreur étrange de Postgres. – WildWezyr

+0

J'ai changé mon code pour le faire un par un avec les mots suivants - sans la table tampon 'spb_word4obj' - maintenant il ressemble à votre proposition. Mais ... il échoue toujours dans différentes itérations de boucle à chaque fois. Il semble que la vérification des passages en double (pas de doublon), mais il est faux, puis le code échoue lors de l'insertion de mot dans «spb_word» en raison de l'enregistrement en double. – WildWezyr

+0

s'il vous plaît regarder ma propre réponse à cette question, il montre le code le plus simple que j'ai pu obtenir pour exposer cette erreur. Je frappe toujours la clé unique constrait et je pense toujours que c'est erreur postgres (pas ma faute). – WildWezyr

Questions connexes