火鸟触发死锁
我对数据库的经验很少,并且遇到过触发器导致 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
发生死锁是因为两个线程尝试同时更新同一行。在这种情况下,最简单的解决方案是使用单个事务和关键部分来防止同时更新。它实际上需要几行代码。
其他方式需要重新设计包含全部信息的表格。
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.
你写:
至少还有一种其他方法。您是否可以简单地在 VIEW 中按需计算它们,而不是使用触发器来预先计算聚合计数和大小,如下所示:(
注意:我猜测您的 HITS 是 ITEMS.ACCESSCOUNT 的总和,正如 UPDATE 触发器和列名称所暗示的那样您没有告诉我们您现在如何记录 MISSES,但是,如果应用程序目前直接递增 STATS.MISSES,您可以为此目的引入一个新表,然后将该表连接到上面的视图中。)
当然,您仍然需要适当地提交事务。上面建议的 STATS 视图仅与它可以看到的已完成、已提交的事务一样准确。
You write:
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:
(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.
这些问题通常更容易解决,方法是使用每次更改的记录 +1 或 -1,并且偶尔(每天、每周)处理整个表以总结所有内容并再次只有一条记录。下一次更改将再次出现 +1 或 -1 记录,您可以查询总和。
因此,您将得到类似的结果:
在计划的合并之后,您将得到:
然后您可以添加一个视图,该视图仅对每个项目的记录进行求和。
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:
and after the scheduled merge you get:
You can then add a view which simply sums the records per item.
使用来修复。
通过在连接字符串中
Fixed by using
in the connection string.