2009-07-31 7 views
1

D'abord, voici quelques scripts pour configurer les tables et l'arrière-plan.Question sur le verrouillage et la récapitulation Oracle

CREATE TABLE TEST_P 
(
    ID NUMBER(3) NOT NULL PRIMARY KEY, 
    SRC VARCHAR2(2) NOT NULL, 
    DEST VARCHAR2(2) NOT NULL, 
    AMT NUMBER(4) NOT NULL, 
    B_ID_SRC NUMBER(3), 
    B_ID_DEST NUMBER(3) 
); 

Une ligne de ce tableau indique que l'AMT est déplacé SRC-DEST. La colonne ID est une clé de substitution. La première rangée indique que 10 choses sont déplacées de B1 à S1. Les valeurs dans SRC et DEST sont différentes - la même valeur ne peut pas apparaître dans les deux.

INSERT INTO TEST_P VALUES (1, 'B1', 'S1', 10, NULL, NULL); 
INSERT INTO TEST_P VALUES (2, 'B2', 'S1', 20, NULL, NULL); 
INSERT INTO TEST_P VALUES (3, 'B3', 'S2', 40, NULL, NULL); 
INSERT INTO TEST_P VALUES (4, 'B1', 'S2', 80, NULL, NULL); 
INSERT INTO TEST_P VALUES (5, 'B4', 'S2', 160,NULL, NULL); 

Il existe un autre tableau de ce type. Il a une vue différente de la même information. Chaque ligne indique ici un élément ajouté ou supprimé de "Qui". Les valeurs de l'OMS sont B1, B2 .. et S1, S2 ...

CREATE TABLE TEST_B 
(
    ID  NUMBER(3) NOT NULL PRIMARY KEY, 
    BATCH NUMBER(3) NOT NULL, 
    WHO  VARCHAR2(2) NOT NULL, 
    AMT  NUMBER(4) NOT NULL 
); 

CREATE SEQUENCE TEST_B_SEQ START WITH 100; 

Besoin d'écrire un processus qui prendra périodiquement des valeurs de TEST_P et remplir TEST_B. Il doit également mettre à jour B_ID_SRC et B_ID_DEST qui sont des clés étrangères dans TEST_B.

Voici ma solution pour l'instant.

Étape 1:

INSERT INTO TEST_B 
(ID, BATCH, WHO, AMT) 
SELECT TEST_B_SEQ.NEXTVAL, 42, WHO, AMT FROM 
(
    SELECT SRC AS WHO, SUM(AMT) AMT FROM TEST_P 
    WHERE B_ID_SRC IS NULL AND B_ID_DEST IS NULL 
    GROUP BY SRC 
    UNION ALL 
    SELECT DEST, -SUM(AMT) FROM TEST_P 
    WHERE B_ID_SRC IS NULL AND B_ID_DEST IS NULL 
    GROUP BY DEST) 
; 

Étape 2:

UPDATE TEST_P 
    SET B_ID_SRC = (SELECT ID FROM TEST_B WHERE BATCH = 42 AND TEST_P.SRC = WHO), 
     B_ID_DEST = (SELECT ID FROM TEST_B WHERE BATCH = 42 AND TEST_P.DEST = WHO); 

Il y a deux problèmes avec ceci:

1) Les lignes SELECT doivent être refermés. Comment puis-je faire cela avec un FOR UPDATE?

2) Si une ligne est insérée par une autre session et validée à l'étape 1.5, alors UPDATE va attraper plus de lignes que l'INSERT. Comment dois-je résoudre ceci sans revenir à un traitement ligne par ligne?

Autres détails La table réelle TEST_P comporte une colonne d'état. C'est seulement quand les choses sont dans le bon état qu'elles sont incluses dans TEST_B.

Pour diverses raisons, TEST_B est réellement nécessaire. Je ne peux pas juste en faire une vue ou quelque chose. Il y a un traitement ultérieur, etc.

Répondre

3

Dans votre exemple, vous allez mettre à jour toutes les lignes de TEST_P. Deux solutions simples vous permettent d'être sûr que les informations sur les deux tables sont cohérentes. Vous pouvez soit:

  1. LOCK TABLE test_p IN EXCLUSIVE MODE pendant toute la durée de la transaction (les autres sessions insertion devraient attendre) ou
  2. ALTER SESSION SET ISOLATION_LEVEL=SERIALIZABLE cela empêcherait la première session pour voir les modifications apportées par d'autres sessions après le début de la transaction.

Méthode 1 est simple, je vais vous montrer la méthode 2:

session 1> ALTER SESSION SET ISOLATION_LEVEL=SERIALIZABLE; 

Session altered 

session 1> INSERT INTO TEST_B 
     2 (ID, BATCH, WHO, AMT) 
     3 SELECT TEST_B_SEQ.NEXTVAL, 42, WHO, AMT FROM 
     4 (
     5 SELECT SRC AS WHO, SUM(AMT) AMT FROM TEST_P 
     6 WHERE B_ID_SRC IS NULL AND B_ID_DEST IS NULL 
     7 GROUP BY SRC 
     8 UNION ALL 
     9 SELECT DEST, -SUM(AMT) FROM TEST_P 
     10 WHERE B_ID_SRC IS NULL AND B_ID_DEST IS NULL 
     11 GROUP BY DEST) 
     12 ; 

6 rows inserted 

Ici, j'insérer une ligne avec une autre session et COMMIT:

session 2> INSERT INTO TEST_P VALUES (6, 'B4', 'S2', 2000,NULL, NULL); 

1 row inserted 

session 2> commit; 

Commit complete 

Session 1 ne voit pas la ligne insérée avec session 2 encore:

session 1> select * from TEST_P; 

    ID SRC DEST AMT B_ID_SRC B_ID_DEST 
---- --- ---- ----- -------- --------- 
    1 B1 S1  10   
    2 B2 S1  20   
    3 B3 S2  40   
    4 B1 S2  80   
    5 B4 S2  16 

session 1> UPDATE TEST_P 
     2 SET B_ID_SRC = (SELECT ID FROM TEST_B WHERE BATCH = 42 AND TEST_P.SRC = WHO), 
     3  B_ID_DEST = (SELECT ID FROM TEST_B WHERE BATCH = 42 AND TEST_P.DEST = WHO); 

5 rows updated 

session 1> commit; 

Commit complete 

Le résultat est cohérent, après la session de validation 1 verra la ligne insérée par la session 2:

session 1> select * from TEST_P; 

    ID SRC DEST AMT B_ID_SRC B_ID_DEST 
---- --- ---- ----- -------- --------- 
    6 B4 S2 2000   
    1 B1 S1  10  100  104 
    2 B2 S1  20  101  104 
    3 B3 S2  40  102  105 
    4 B1 S2  80  100  105 
    5 B4 S2  160  103  105 

6 rows selected 
+0

Merci Vincent, c'est ce dont j'avais besoin. Dans la réalité, je ne peux pas bloquer toute cette table parce qu'il y a d'autres choses qui ne vont pas là-bas. La transaction sérialisable est ce dont j'ai besoin. –

1

Une seule instruction MERGE peut gérer l'exigence ici.

http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/statements_9016.htm#SQLRF01606

Votre déclaration serait quelque chose le long des lignes de:

MERGE INTO TEST_B 
USING 
(
    SELECT SRC AS WHO, SUM(AMT) AMT FROM TEST_P 
    WHERE B_ID_SRC IS NULL AND B_ID_DEST IS NULL 
    GROUP BY SRC 
    UNION ALL 
    SELECT DEST, -SUM(AMT) FROM TEST_P 
    WHERE B_ID_SRC IS NULL AND B_ID_DEST IS NULL 
    GROUP BY DEST) 
ON (
WHEN MATCHED THEN UPDATE SET ...; 

Il pourrait être plus effiecient pour identifier les lignes qui doivent être mis à jour si une jointure à la table cible dans l'UTILISATION clause, pour éviter la mise à jour des lignes qui n'ont pas besoin d'être modifiées.

+0

@David: Une seule instruction MERGE ne pourra pas modifier les deux tables (c'est-à-dire: insérer les lignes dans TEST_B et mettre à jour TEST_P) –

+0

Oh désolé, tout à fait. –