mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-05-29 08:55:36 -04:00
INSERT INTO Event_Summaries_New SELECT * FROM VIEW_Event_Summaries took shared next-key locks on the source Events table (required for STATEMENT binlog correctness under the default REPEATABLE READ). Concurrent DELETE FROM Events deadlocked against those S locks. Switch the procedure to READ COMMITTED so the source-side SELECT runs as a consistent snapshot read with no row locks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
195 lines
6.5 KiB
SQL
195 lines
6.5 KiB
SQL
DELIMITER //
|
|
|
|
-- This index covers MonitorId grouping, StartDateTime ranges,
|
|
-- Archived status filtering, and includes DiskSpace for summation.
|
|
SET @s = (SELECT IF(
|
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS WHERE table_schema = DATABASE()
|
|
AND table_name = 'Events'
|
|
AND index_name = 'Events_Summaries_Performance_idx'
|
|
) > 0,
|
|
"SELECT 'Events_Summaries_Performance_idx already exists'",
|
|
"CREATE INDEX Events_Summaries_Performance_idx ON Events (MonitorId, StartDateTime, Archived, DiskSpace)"
|
|
))//
|
|
PREPARE stmt FROM @s//
|
|
EXECUTE stmt//
|
|
DEALLOCATE PREPARE stmt//
|
|
|
|
-- Clean up a previous typoed version of the index
|
|
SET @s = (SELECT IF(
|
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS WHERE table_schema = DATABASE()
|
|
AND table_name = 'Events'
|
|
AND index_name = 'Events_Summaries_Perfomance_idx'
|
|
) > 0,
|
|
"DROP INDEX Events_Summaries_Perfomance_idx ON Events",
|
|
"SELECT 'Events_Summaries_Perfomance_idx does not exist'"
|
|
))//
|
|
PREPARE stmt FROM @s//
|
|
EXECUTE stmt//
|
|
DEALLOCATE PREPARE stmt//
|
|
|
|
-- Hourly Events View
|
|
CREATE OR REPLACE VIEW Events_Hour AS
|
|
SELECT
|
|
Id AS EventId,
|
|
MonitorId,
|
|
DiskSpace,
|
|
StartDateTime
|
|
FROM Events
|
|
WHERE StartDateTime >= (NOW() - INTERVAL 1 HOUR)//
|
|
|
|
-- Daily Events View
|
|
CREATE OR REPLACE VIEW Events_Day AS
|
|
SELECT
|
|
Id AS EventId,
|
|
MonitorId,
|
|
DiskSpace,
|
|
StartDateTime
|
|
FROM Events
|
|
WHERE StartDateTime >= (NOW() - INTERVAL 1 DAY)//
|
|
|
|
-- Weekly Events View
|
|
CREATE OR REPLACE VIEW Events_Week AS
|
|
SELECT
|
|
Id AS EventId,
|
|
MonitorId,
|
|
DiskSpace,
|
|
StartDateTime
|
|
FROM Events
|
|
WHERE StartDateTime >= (NOW() - INTERVAL 7 DAY)//
|
|
|
|
-- Monthly Events View
|
|
CREATE OR REPLACE VIEW Events_Month AS
|
|
SELECT
|
|
Id AS EventId,
|
|
MonitorId,
|
|
DiskSpace,
|
|
StartDateTime
|
|
FROM Events
|
|
WHERE StartDateTime >= (NOW() - INTERVAL 1 MONTH)//
|
|
|
|
-- Archived Events View
|
|
CREATE OR REPLACE VIEW Events_Archived AS
|
|
SELECT
|
|
Id AS EventId,
|
|
MonitorId,
|
|
DiskSpace
|
|
FROM Events
|
|
WHERE Archived = 1//
|
|
|
|
-- Event Summaries Source View (used by the refresh procedure)
|
|
CREATE OR REPLACE VIEW VIEW_Event_Summaries AS
|
|
SELECT
|
|
MonitorId,
|
|
-- Hour Stats
|
|
COUNT(CASE WHEN StartDateTime >= (NOW() - INTERVAL 1 HOUR) THEN 1 END) AS HourEvents,
|
|
COALESCE(SUM(CASE WHEN StartDateTime >= (NOW() - INTERVAL 1 HOUR) THEN DiskSpace ELSE 0 END), 0) AS HourEventDiskSpace,
|
|
|
|
-- Day Stats
|
|
COUNT(CASE WHEN StartDateTime >= (NOW() - INTERVAL 1 DAY) THEN 1 END) AS DayEvents,
|
|
COALESCE(SUM(CASE WHEN StartDateTime >= (NOW() - INTERVAL 1 DAY) THEN DiskSpace ELSE 0 END), 0) AS DayEventDiskSpace,
|
|
|
|
-- Week Stats
|
|
COUNT(CASE WHEN StartDateTime >= (NOW() - INTERVAL 7 DAY) THEN 1 END) AS WeekEvents,
|
|
COALESCE(SUM(CASE WHEN StartDateTime >= (NOW() - INTERVAL 7 DAY) THEN DiskSpace ELSE 0 END), 0) AS WeekEventDiskSpace,
|
|
|
|
-- Month Stats
|
|
COUNT(CASE WHEN StartDateTime >= (NOW() - INTERVAL 1 MONTH) THEN 1 END) AS MonthEvents,
|
|
COALESCE(SUM(CASE WHEN StartDateTime >= (NOW() - INTERVAL 1 MONTH) THEN DiskSpace ELSE 0 END), 0) AS MonthEventDiskSpace,
|
|
|
|
-- Archive Stats
|
|
COUNT(CASE WHEN Archived = 1 THEN 1 END) AS ArchivedEvents,
|
|
COALESCE(SUM(CASE WHEN Archived = 1 THEN DiskSpace ELSE 0 END), 0) AS ArchivedEventDiskSpace,
|
|
|
|
-- Totals
|
|
COUNT(Id) AS TotalEvents,
|
|
COALESCE(SUM(DiskSpace), 0) AS TotalEventDiskSpace
|
|
FROM Events
|
|
GROUP BY MonitorId//
|
|
|
|
-- Event Summaries snapshot table (SWR pattern)
|
|
CREATE TABLE IF NOT EXISTS `Event_Summaries` (
|
|
`MonitorId` int(10) unsigned NOT NULL,
|
|
`HourEvents` int(10) DEFAULT 0,
|
|
`HourEventDiskSpace` bigint DEFAULT 0,
|
|
`DayEvents` int(10) DEFAULT 0,
|
|
`DayEventDiskSpace` bigint DEFAULT 0,
|
|
`WeekEvents` int(10) DEFAULT 0,
|
|
`WeekEventDiskSpace` bigint DEFAULT 0,
|
|
`MonthEvents` int(10) DEFAULT 0,
|
|
`MonthEventDiskSpace` bigint DEFAULT 0,
|
|
`ArchivedEvents` int(10) DEFAULT 0,
|
|
`ArchivedEventDiskSpace` bigint DEFAULT 0,
|
|
`TotalEvents` int(10) DEFAULT 0,
|
|
`TotalEventDiskSpace` bigint DEFAULT 0,
|
|
PRIMARY KEY (`MonitorId`)
|
|
) ENGINE=InnoDB//
|
|
|
|
-- Metadata table for SWR staleness tracking
|
|
CREATE TABLE IF NOT EXISTS `Event_Summaries_Metadata` (
|
|
`table_name` VARCHAR(64) NOT NULL,
|
|
`last_updated` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00',
|
|
PRIMARY KEY (`table_name`)
|
|
) ENGINE=InnoDB//
|
|
|
|
INSERT IGNORE INTO `Event_Summaries_Metadata`
|
|
(`table_name`, `last_updated`) VALUES ('Event_Summaries', '1970-01-01 00:00:00')//
|
|
|
|
-- Stored procedure: atomic refresh of Event_Summaries with GET_LOCK to prevent thundering herd
|
|
DROP PROCEDURE IF EXISTS `Refresh_Summaries_SWR`//
|
|
|
|
CREATE PROCEDURE `Refresh_Summaries_SWR`()
|
|
proc: BEGIN
|
|
DECLARE v_lock_result INT DEFAULT 0;
|
|
DECLARE v_last DATETIME;
|
|
|
|
-- Use READ COMMITTED so the INSERT...SELECT below performs a consistent
|
|
-- snapshot read on Events without taking shared next-key locks. Under the
|
|
-- default REPEATABLE READ (and STATEMENT binlog format) the source-side
|
|
-- locks deadlock with concurrent DELETE FROM Events.
|
|
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
|
|
|
-- Non-blocking lock: skip if another process is already refreshing
|
|
SET v_lock_result = GET_LOCK('refresh_summaries_lock', 0);
|
|
IF v_lock_result != 1 THEN
|
|
-- Another process holds the lock; return immediately (stale read is fine)
|
|
LEAVE proc;
|
|
END IF;
|
|
|
|
-- Double-check staleness inside lock
|
|
SELECT `last_updated` INTO v_last
|
|
FROM `Event_Summaries_Metadata`
|
|
WHERE `table_name` = 'Event_Summaries';
|
|
|
|
IF v_last IS NOT NULL AND TIMESTAMPDIFF(SECOND, v_last, NOW()) < 60 THEN
|
|
DO RELEASE_LOCK('refresh_summaries_lock');
|
|
LEAVE proc;
|
|
END IF;
|
|
|
|
-- Atomic rename pattern: build new table, swap, drop old
|
|
DROP TABLE IF EXISTS `Event_Summaries_New`;
|
|
CREATE TABLE `Event_Summaries_New` LIKE `Event_Summaries`;
|
|
INSERT INTO `Event_Summaries_New` SELECT * FROM `VIEW_Event_Summaries`;
|
|
|
|
DROP TABLE IF EXISTS `Event_Summaries_Old`;
|
|
RENAME TABLE `Event_Summaries` TO `Event_Summaries_Old`,
|
|
`Event_Summaries_New` TO `Event_Summaries`;
|
|
DROP TABLE IF EXISTS `Event_Summaries_Old`;
|
|
|
|
-- Update metadata timestamp
|
|
UPDATE `Event_Summaries_Metadata`
|
|
SET `last_updated` = NOW()
|
|
WHERE `table_name` = 'Event_Summaries';
|
|
|
|
DO RELEASE_LOCK('refresh_summaries_lock');
|
|
END proc//
|
|
|
|
-- MySQL EVENT for background refresh every 600 seconds (10 minutes)
|
|
-- Note: Requires event_scheduler=ON in my.cnf or SET GLOBAL event_scheduler = ON;
|
|
DROP EVENT IF EXISTS `Event_Summaries_Refresh_Event`//
|
|
|
|
CREATE EVENT IF NOT EXISTS `Event_Summaries_Refresh_Event`
|
|
ON SCHEDULE EVERY 600 SECOND
|
|
DO CALL `Refresh_Summaries_SWR`()//
|
|
|
|
DELIMITER ;
|