火鸟触发死锁

发布于 2024-12-10 08:38:34 字数 2642 浏览 0 评论 0原文

我对数据库的经验很少,并且遇到过触发器导致 firebird 2.5 数据库死锁的问题。

数据库中有两个表。当从 ITEMS 表中添加或删除项目时,触发器会更新 STATS.ITEMCOUNT 和 STATS.ITEMCOUNT 。统计数据大小。总共有 4 个触发器,其中 2 个增量和 2 个触发器。和两个递减。

统计表只有一行,用于跟踪数据库中的内容。我这样做的方式不对吗?如果没有,是否有解决办法。

死锁发生在启动应用程序的最初几分钟内。

UPDATE1:发布所有触发器。

UPDATE2:发布的 ExecuteNonQuery 方法

UPDATE3:即使使用 pilcrow 善意建议的视图方法,死锁仍然会发生。事实上,我什至尝试使用再次陷入僵局的存储过程。将 select 语句包装在事务中也失败,因为 Firebird Ado 提供程序不支持并行事务。

public void ExecuteNonQuery(string NonQuery)
        {
            try
            {
                FbCommand FBC = new FbCommand(NonQuery, DBConnection);
                FBC.ExecuteNonQuery();
                FBC.Dispose();
            }
            catch (FbException e)
            {
                Log.FatalException("Database NonQuery Error", e);
            }

        }

        }

数据库

** Tables **

CREATE TABLE ITEMS (
    ID              ID NOT NULL /* ID = VARCHAR(36) NOT NULL */,
    EXPIRYTIME      EXPIRYTIME NOT NULL /* EXPIRYTIME = BIGINT NOT NULL */,
    ITEMSIZE        ITEMSIZE /* ITEMSIZE = BIGINT NOT NULL */,
    ACCESSCOUNT     ACCESSCOUNT DEFAULT 1 NOT NULL /* ACCESSCOUNT = INTEGER DEFAULT 1 NOT NULL */,
    LASTACCESSTIME  LASTACCESSTIME /* LASTACCESSTIME = TIMESTAMP NOT NULL */
);


CREATE TABLE STATS (
    INSTANCE            SMALLINT,
    SIZE                BIGINT DEFAULT 0,
    ITEMCOUNT  BIGINT DEFAULT 0,
    HITS       BIGINT DEFAULT 0,
    MISSES     BIGINT DEFAULT 0
);

** 触发器 **

   /* Trigger: TRG_INCREMENT_ITEMCOUNT_STATS */
CREATE OR ALTER TRIGGER TRG_INCREMENT_ITEMCOUNT_STATS FOR ITEMS
ACTIVE AFTER INSERT POSITION 1
AS
begin
 UPDATE STATS SET ITEMCOUNT = ITEMCOUNT + 1 WHERE INSTANCE = '0';
end

/* Trigger: TRG_DECREMENT_ITEMCOUNT_STATS */
CREATE OR ALTER TRIGGER TRG_DECREMENT_ITEMCOUNT_STATS FOR ITEMS
ACTIVE AFTER DELETE POSITION 2
AS
begin
UPDATE STATS SET ITEMCOUNT = ITEMCOUNT - 1 WHERE INSTANCE = '0';
end

/* Trigger: TRG_INCREMENT_HITS_STATS */
CREATE OR ALTER TRIGGER TRG_INCREMENT_HITS_STATS FOR ITEMS
ACTIVE AFTER UPDATE POSITION 3
AS
begin
UPDATE STATS SET HITS = HITS + 1 WHERE INSTANCE = '0';
end

/* Trigger: TRG_INCREMENT_SIZE_STATS */
CREATE OR ALTER TRIGGER TRG_INCREMENT_SIZE_STATS FOR ITEMS
ACTIVE AFTER INSERT POSITION 4
AS
BEGIN
  UPDATE STATS SET SIZE = SIZE + NEW.ITEMSIZE WHERE INSTANCE = 0;
END

/* Trigger: TRG_DECREMENT_CACHESIZE_STATS */
CREATE OR ALTER TRIGGER TRG_DECREMENT_CACHESIZE_STATS FOR ITEMS
ACTIVE AFTER DELETE POSITION 5
AS
BEGIN
  UPDATE STATS SET SIZE = SIZE - OLD.ITEMSIZE WHERE INSTANCE = 0;
END

I've very little experience with databases and have run in to a problem with a trigger causing a deadlock in a firebird 2.5 database.

There is two tables in the database . When items are added or removed from the ITEMS table a Trigger updates STATS.ITEMCOUNT & STATS.SIZE. There are 4 triggers in total 2 Incrementing & and two decrementing.

The stats table has a single row and is used to track what in the database. Am I doing this the wrong way? And If not is there a work round.

A deadlock occurs within the first few minutes of starting the application.

UPDATE1: Posted all triggers.

UPDATE2: Posted ExecuteNonQuery Method

UPDATE3: Deadlocks still occur even when using the view method kindly suggested by pilcrow. In fact I even tried using a stored procedure which deadlocked again. Wrapping the select statement in a transaction also failed because the Firebird Ado provider does not support parallel transactions.

public void ExecuteNonQuery(string NonQuery)
        {
            try
            {
                FbCommand FBC = new FbCommand(NonQuery, DBConnection);
                FBC.ExecuteNonQuery();
                FBC.Dispose();
            }
            catch (FbException e)
            {
                Log.FatalException("Database NonQuery Error", e);
            }

        }

        }

Database

** Tables **

CREATE TABLE ITEMS (
    ID              ID NOT NULL /* ID = VARCHAR(36) NOT NULL */,
    EXPIRYTIME      EXPIRYTIME NOT NULL /* EXPIRYTIME = BIGINT NOT NULL */,
    ITEMSIZE        ITEMSIZE /* ITEMSIZE = BIGINT NOT NULL */,
    ACCESSCOUNT     ACCESSCOUNT DEFAULT 1 NOT NULL /* ACCESSCOUNT = INTEGER DEFAULT 1 NOT NULL */,
    LASTACCESSTIME  LASTACCESSTIME /* LASTACCESSTIME = TIMESTAMP NOT NULL */
);


CREATE TABLE STATS (
    INSTANCE            SMALLINT,
    SIZE                BIGINT DEFAULT 0,
    ITEMCOUNT  BIGINT DEFAULT 0,
    HITS       BIGINT DEFAULT 0,
    MISSES     BIGINT DEFAULT 0
);

** Triggers **

   /* Trigger: TRG_INCREMENT_ITEMCOUNT_STATS */
CREATE OR ALTER TRIGGER TRG_INCREMENT_ITEMCOUNT_STATS FOR ITEMS
ACTIVE AFTER INSERT POSITION 1
AS
begin
 UPDATE STATS SET ITEMCOUNT = ITEMCOUNT + 1 WHERE INSTANCE = '0';
end

/* Trigger: TRG_DECREMENT_ITEMCOUNT_STATS */
CREATE OR ALTER TRIGGER TRG_DECREMENT_ITEMCOUNT_STATS FOR ITEMS
ACTIVE AFTER DELETE POSITION 2
AS
begin
UPDATE STATS SET ITEMCOUNT = ITEMCOUNT - 1 WHERE INSTANCE = '0';
end

/* Trigger: TRG_INCREMENT_HITS_STATS */
CREATE OR ALTER TRIGGER TRG_INCREMENT_HITS_STATS FOR ITEMS
ACTIVE AFTER UPDATE POSITION 3
AS
begin
UPDATE STATS SET HITS = HITS + 1 WHERE INSTANCE = '0';
end

/* Trigger: TRG_INCREMENT_SIZE_STATS */
CREATE OR ALTER TRIGGER TRG_INCREMENT_SIZE_STATS FOR ITEMS
ACTIVE AFTER INSERT POSITION 4
AS
BEGIN
  UPDATE STATS SET SIZE = SIZE + NEW.ITEMSIZE WHERE INSTANCE = 0;
END

/* Trigger: TRG_DECREMENT_CACHESIZE_STATS */
CREATE OR ALTER TRIGGER TRG_DECREMENT_CACHESIZE_STATS FOR ITEMS
ACTIVE AFTER DELETE POSITION 5
AS
BEGIN
  UPDATE STATS SET SIZE = SIZE - OLD.ITEMSIZE WHERE INSTANCE = 0;
END

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

老娘不死你永远是小三 2024-12-17 08:38:34

发生死锁是因为两个线程尝试同时更新同一行。在这种情况下,最简单的解决方案是使用单个事务和关键部分来防止同时更新。它实际上需要几行代码。

其他方式需要重新设计包含全部信息的表格。

Deadlock occurs because two threads attempt to update the same row simultaneously. Simpliest solution in this case would be to use single transaction and critical section to prevent simultaneous updates. It would require literally a couple of lines of code.

Other way would require redesign of a table with total information.

全部不再 2024-12-17 08:38:34

你写:

...有解决办法吗。

至少还有一种其他方法。您是否可以简单地在 VIEW 中按需计算它们,而不是使用触发器来预先计算聚合计数和大小,如下所示:(

CREATE VIEW stats (instance, size, itemcount, hits, misses) AS
   SELECT CAST(0 AS SMALLINT),
          CAST(COALESCE(SUM(items.itemsize), 0) AS BIGINT),
          CAST(COUNT('x') AS BIGINT),
          CAST(COALESCE(SUM(items.accesscount), 0) AS BIGINT), -- just guessing here
          0                                                    -- but see below
     FROM items;

注意:我猜测您的 HITS 是 ITEMS.ACCESSCOUNT 的总和,正如 UPDATE 触发器和列名称所暗示的那样您没有告诉我们您现在如何记录 MISSES,但是,如果应用程序目前直接递增 STATS.MISSES,您可以为此目的引入一个表,然后将该表连接到上面的视图中。)

当然,您仍然需要适当地提交事务。上面建议的 STATS 视图仅与它可以看到的已完成、已提交的事务一样准确。

You write:

... is there a work round.

There is at least one other approach. Instead of using triggers to precompute aggregated counts and sizes, could you simply compute them on demand in a VIEW like so:

CREATE VIEW stats (instance, size, itemcount, hits, misses) AS
   SELECT CAST(0 AS SMALLINT),
          CAST(COALESCE(SUM(items.itemsize), 0) AS BIGINT),
          CAST(COUNT('x') AS BIGINT),
          CAST(COALESCE(SUM(items.accesscount), 0) AS BIGINT), -- just guessing here
          0                                                    -- but see below
     FROM items;

(Note: I'm guessing that your HITS are the sum of ITEMS.ACCESSCOUNT, as the UPDATE trigger and column names suggest. You don't tell us how you are recording MISSES now, but, if the application is directly incrementing STATS.MISSES at present, you could introduce a new table for that purpose, and then JOIN that table into the VIEW above.)

You will still have to commit transactions appropriately, of course. The STATS view suggested above is only as accurate as completed, committed transactions it can see.

狼性发作 2024-12-17 08:38:34

这些问题通常更容易解决,方法是使用每次更改的记录 +1 或 -1,并且偶尔(每天、每周)处理整个表以总结所有内容并再次只有一条记录。下一次更改将再次出现 +1 或 -1 记录,您可以查询总和。

因此,您将得到类似的结果:

ITEM  COUNT
item1 10
item2 10
item1 1
item2 -1
item2 -1
item1 -1

在计划的合并之后,您将得到:

ITEM   COUNT
item1  10
item2  8

然后您可以添加一个视图,该视图仅对每个项目的记录进行求和。

These problems are usually easier solved by using a record per change with a +1 or -1, and occassionaly (daily, weekly) processing the entire table to sum everything up and have only one record again. Next change will then again a +1 or -1 record and you query the sum total.

So you would have something like:

ITEM  COUNT
item1 10
item2 10
item1 1
item2 -1
item2 -1
item1 -1

and after the scheduled merge you get:

ITEM   COUNT
item1  10
item2  8

You can then add a view which simply sums the records per item.

油饼 2024-12-17 08:38:34

使用来修复。

.IsolationLevel = IsolationLevel.ReadUncommitted;

通过在连接字符串中

Fixed by using

.IsolationLevel = IsolationLevel.ReadUncommitted;

in the connection string.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文