【问题标题】:Oracle mutating table errorOracle 变异表错误
【发布时间】:2015-02-15 09:57:39
【问题描述】:

我有一个

posts(id, likes)(likes = number of likes for that post)

还有一个

post_likes(post_id, user_id)(the users who liked a post)

我有一个触发器(post_likes_after_insert)

所以当我添加一个新的类似它时,它会增加posts.likes 和另一个触发器(post_likes_before_delete),它会减少posts.likes。一切正常。

然后我有另一个触发器(before delete) 用于删除post_likes,但这将触发上面的触发器减少posts.likes 列。

所以它给了我这个错误:

posts 正在发生变化,触发器/函数可能看不到它 post_likes_before_delete,第 4 行;执行触发器时出错 post_likes_before_delete

这是一个带有代码的 pastebin,底部有完整的错误。 http://pastebin.com/EQHTLZFp

谢谢。

----------------------------------------------------------------------- 
--  USERS
-----------------------------------------------------------------------
CREATE TABLE users
(
        id NUMBER(10) PRIMARY KEY NOT NULL,
        email CHAR(100) UNIQUE NOT NULL,
        password CHAR(60) NOT NULL,
        first_name CHAR(30) NOT NULL,
        last_name CHAR(30) NOT NULL,
        birth_date DATE NULL,
        gender CHAR(10) NULL,
        joined TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
        CONSTRAINT gender_check CHECK (UPPER(gender) IN ('M', 'F'))
);

-- Auto increment for users.id
DROP SEQUENCE users_sequence;
CREATE SEQUENCE users_sequence START WITH 1 INCREMENT BY 1;
CREATE TRIGGER users_before_insert 
BEFORE INSERT
        ON users REFERENCING NEW AS NEW 
        FOR EACH ROW
BEGIN 
        SELECT users_sequence.nextval INTO :NEW.ID FROM dual; 
END;
/

-- 
-- POSTS
-- 
CREATE TABLE posts
(
        id NUMBER(10) PRIMARY KEY NOT NULL,
        user_id NUMBER(10) REFERENCES users(id) NOT NULL,
        created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
        content char(1000) NOT NULL,
        likes NUMBER(10) DEFAULT 0 NOT NULL,
        privacy CHAR(10) DEFAULT 'public' NOT NULL,
        CONSTRAINT fk_posts FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- Auto increment for posts.id
DROP SEQUENCE posts_sequence;
CREATE SEQUENCE posts_sequence START WITH 1 INCREMENT BY 1;
CREATE TRIGGER posts_before_insert 
BEFORE INSERT
        ON posts REFERENCING NEW AS NEW 
        FOR EACH ROW
BEGIN 
        SELECT posts_sequence.nextval INTO :NEW.ID FROM dual; 
END;
/

CREATE TRIGGER posts_before_delete 
BEFORE DELETE
        ON posts
        FOR EACH ROW
BEGIN
        DELETE FROM post_likes WHERE post_id = :old.id;
END;
/

-- 
-- POST_LIKES
-- 
CREATE TABLE post_likes
(
        user_id NUMBER(10) NOT NULL REFERENCES users(id),
        post_id NUMBER(10) NOT NULL REFERENCES posts(id),
        PRIMARY KEY (user_id, post_id),
        --CONSTRAINT fk_post_likes1 FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
        CONSTRAINT fk_post_likes2 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- Increments posts.likes  
CREATE TRIGGER post_likes_after_insert 
AFTER INSERT
        ON post_likes
        FOR EACH ROW

DECLARE
   n NUMBER(10);
BEGIN 
        SELECT likes INTO n FROM posts WHERE id = :new.post_id;
        UPDATE posts SET likes = n+1 WHERE id = :new.post_id;
END;
/

-- Decrements posts.likes
CREATE TRIGGER post_likes_before_delete
BEFORE DELETE
        ON post_likes
        FOR EACH ROW

DECLARE
   n NUMBER(10);
BEGIN 
        SELECT likes INTO n FROM posts WHERE id = :old.post_id;
        UPDATE posts SET likes = n-1 WHERE id = :old.post_id;
END;
/

-- Full error, EUSEBIU is the username that I'm logged in
-- ERROR at line 1:
-- ORA-04091: table EUSEBIU.POSTS is mutating, trigger/function may not see it
-- ORA-06512: at "EUSEBIU.POST_LIKES_BEFORE_DELETE", line 4
-- ORA-04088: error during execution of trigger 'EUSEBIU.POST_LIKES_BEFORE_DELETE'
-- ORA-06512: at "EUSEBIU.POSTS_BEFORE_DELETE", line 2
-- ORA-04088: error during execution of trigger 'EUSEBIU.POSTS_BEFORE_DELETE'

【问题讨论】:

  • 首先,您确定需要以这种方式对数据进行非规范化吗?假设您出于性能原因确实需要物化该数据,而不是说,有一个物化视图会定期进行增量刷新以物化点赞数。如果您确实需要具体化数据,您是否确定确实需要使用触发器来维护数据,而不是将该逻辑放在应用层(即实现适当业务逻辑的存储过程)?
  • 这只是 SQL 实现(我没有在应用程序中使用实际的数据库),我认为有这个更新喜欢的触发器会很好,但我会坚持COUNT * FROM 获取点赞数。
  • 我不确定“我没有在应用程序中使用实际数据库”是什么意思——如果您使用触发器,那么您已经离开 SQL 并转移到 PL/SQL。如果您使用的是 PL/SQL,我不清楚为什么一个 PL/SQL 解决方案(触发器)可以接受,而另一个(存储过程)则不能。从这个角度来看,物化视图会更好,因为它实际上是一个纯 SQL 解决方案。如果出于某种原因必须使用触发器,那是可能的。就简单性和可维护性而言,这不是我最喜欢的 10 种方法。
  • 在这里发布代码。链接可能会随着时间的推移而中断,然后您的问题对其他人没有帮助。
  • 也许我误解了一些东西,但 IMO 你不应该使用触发器来强制引用完整性。如果我了解您的设计,那么 POST_LIKES.POST_ID 上应该有一个引用 POSTS.ID 的外键约束。通过设置此外键,使其删除操作为 CASCADE,当您从 POSTS 中删除时,FK 约束应负责删除所有 POST_LIKES。祝你好运。

标签: oracle plsql triggers


【解决方案1】:

看看如果你删除帖子会发生什么。您的触发器尝试删除 post_likes。这就是为什么您的其他触发器会尝试按照您的意愿减少posts.likes。因此,您正在尝试进行不必要的操作并看到突变,因为删除帖子会导致更新帖子。

你应该做的就是打破这个链条。当您删除帖子时,您不应该尝试更改喜欢的内容。例如,您可以使用类似

create or replace trigger MY_BEFORE_DELETE_LIKE_TRIGGER ...
begin
  -- Let's do nothing if deleting master too
  if dbms_utility.format_call_stack like '%MY_BEFORE_DELETE_POSTS_TRIGGER%' then
    return;
  end if;
  ...

更正确的方法是在打包变量中使用标志或类似的东西。

【讨论】:

    猜你喜欢
    • 2018-10-20
    • 1970-01-01
    • 1970-01-01
    • 2015-06-17
    • 2016-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多