有关Oracle锁定和摘要的问题

时间:2009-07-31 06:43:51

标签: oracle locking

首先,这里有一些设置表格和背景的脚本。

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)
);

此表格中的一行表示AMT正在从SRC移至DESTID列是代理键。第一行表示10件物品从B1移动到S1。 SRCDEST中的值不同 - 两者中都不会显示相同的值。

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);

还有另一张这样的表格。它对相同信息有不同的看法。这里的每一行表示从“谁”添加或删除的内容。世卫组织的数值是B1,B2 ..和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;

需要编写一个定期从TEST_P获取值并填充TEST_B的流程。它还必须更新 B_ID_SRCB_ID_DESTTEST_B的外键。

到目前为止,这是我的解决方案。

第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)
;

第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);

这有两个问题:

1)应锁定SELECT中的行。如何选择FOR UPDATE

2)如果一个行被另一个会话插入并在步骤1.5提交,那么UPDATE将捕获比INSERT更多的行。如何在不恢复逐行处理的情况下解决此问题?

更多详情 真正的TEST_P表上有一个状态列。只有当事情处于正确状态时,才会将它们包含在TEST_B中。

由于各种原因TEST_B实际上是必需的。我不能只是让它成为一种观点或其他东西。有后续处理等。

2 个答案:

答案 0 :(得分:3)

在您的例子中,您将更新TEST_P的所有行。两个简单的解决方案可以让您确保两个表上的信息是一致的。你可以:

    在交易期间
  1. LOCK TABLE test_p IN EXCLUSIVE MODE(其他插入会话必须等待)或
  2. ALTER SESSION SET ISOLATION_LEVEL=SERIALIZABLE这会阻止第一个会话在事务开始后看到其他会话所做的更改。
  3. 方法1很简单,我将演示方法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
    

    这里我在另一个会话中插入一行并提交:

    session 2> INSERT INTO TEST_P VALUES (6, 'B4', 'S2', 2000,NULL, NULL);
    
    1 row inserted
    
    session 2> commit;
    
    Commit complete
    

    会话1未看到已插入会话2的行:

    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
    

    结果是一致的,在提交会话1将看到会话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
    

答案 1 :(得分:1)

单个MERGE语句可以在此处理此要求。

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

你的陈述将是:

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 ...;

通过USING子句中的目标表的连接来识别需要更新的行可能更有效,以避免更新不需要修改的行。