For the sake of simplicity, triggers are the way to go for implementing any kind of tracking of database changes. However, you need to be aware of what happens under the hood when you use triggers.
According to MySQL Stored Procedure Programming, page 256 under the head "Trigger Overhead" says the following:
It is important to remember that, by necessity, triggers add overhead
to the DML statement to which they apply. the actual amount of overhead
will depend upon the nature of the trigger, but --- as all MySQL
triggers execute FOR EACH ROW --- the overhead can rapidly accumulate
for statements that process large numbers of rows. You should
therefore avoid placing any expensive SQL statements or procedural
code in triggers.
An expanded explanation of trigger overhead is given on pages 529-531. The conclulding point from that section states the following:
The lesson here is this: since the trigger code will execute once
for every row affected by a DML statement, the trigger can easily
become the most significant factor in DML performance. Code inside the
trigger body needs to be as lightweight as possible and -- in
particular -- any SQL statements in the trigger should be supported by
indexes whenever possible.
Not mentioned in the book is another factor when using triggers: When it comes to audit logging, please be aware of what you log data into. I say this because should you choose to log to a MyISAM table, each INSERT into a MyISAM table produces a full table lock during the INSERT. This can become a serious bottleneck in a high-traffic, high-transaction environment. Additionally, if the trigger is against an InnoDB table and you log changes in MyISAM from within the trigger, this will secretly disabled ACID compliance (i.e., reduce block transactions to autocommit behavior), which is cannot be rolled back.
When using triggers on InnoDB tables and logging changes
- The table you log to is also InnoDB
- You have autocommit turned off
- You setup START TRANSACTION...COMMIT/ROLLBACK blocks thoroughly
In this way, audit logs can benefit from COMMIT/ROLLBACK as would main tables.
Concerning using stored procedures, you would have to painstakingly call the stored procedure at every point of DML against the table being tracked. One could easily miss logging changes in the face of tens of thousands of lines of application code. Placing such code in a trigger eliminates finding all those DML statements.
CAVEAT
Depending on how complex the trigger is, it can still be a bottleneck. If you want to reduce bottlenecks in audit logging, there is something you can do. However, it will require a little infrastructure change.
Using commodity hardware, create two more DB Servers
This will server to reduce write I/O on the main database (MD) due to audit logging. Here is how you can accomplish it:
Step 01) Turn on binary logging in the main database.
Step 02) Using an inexpensive server, setup MySQL (same version as MD) with binary logging enabled. This will be DM. Setup replication from MD to DM.
Step 03) Using a second inexpensive server, setup MySQL (same version as MD) with binary logging disabled. Setup each audit table to use --replicate-do-table. This will be AU. Setup replication from DM to AU.
Step 04) mysqldump the table structures from MD and load it into DM and AU.
Step 05) Convert all audit tables in MD to use the BLACKHOLE storage engine
Step 06) Convert all tables in DM and AU to use the BLACKHOLE storage engine
Step 07) Convert all audit tables in AU to use the MyISAM storage engine
When done
- DM will replicate from MD and record stuff in its binary log only
- With --replicate-do-table filter on all audit tables, AU will replicate from DM
What this does is store audit info on a separate DB server and also reduce any write I/O degradation that MD would normally have.
Actually, if you place an if then block in every trigger, you could effectively shutdown all triggers. Here is such a code block
IF @TRIGGER_DISABLED = 0 THEN
...trigger body
END IF;
In the mysql environment, you could
- run
SET @TRIGGER_DISABLED = 1;
- do your data maintenance
- run
SET @TRIGGER_DISABLED = 0;
So your trigger for table A should look like this:
BEGIN
IF @TRIGGER_DISABLED = 0 THEN
IF (OLD.status != 1 AND NEW.status = 2) THEN
IF (OLD.geo_lat IS NOT NULL AND OLD.geo_long IS NOT NULL) THEN
DELETE FROM geo WHERE datatype IN (3,4) AND foreignid = NEW.id;
END IF;
ELSEIF (OLD.Status = 1 AND NEW.Status != 2) THEN
IF (NEW.geo_lat IS NOT NULL AND NEW.geo_long IS NOT NULL) THEN
INSERT INTO geo (datatype, foreignid, long, lat, hostid, morton, status) VALUES (IF(NEW.groupType=1,3,4), NEW.id, NEW.geo_long, NEW.geo_lat, NEW.hostid, 0, NEW.Status);
END IF;
ELSEIF (NEW.status != 3) THEN
IF (OLD.geo_lat IS NOT NULL AND OLD.geo_long IS NOT NULL AND (NEW.geo_lat IS NULL OR NEW.geo_long IS NULL)) THEN
DELETE FROM geo WHERE datatype IN (3,4) AND foreignid = NEW.id;
ElSEIF ((OLD.geo_lat IS NULL OR OLD.geo_long IS NULL) AND NEW.geo_lat IS NOT NULL AND NEW.geo_long IS NOT NULL) THEN
INSERT INTO geo (datatype, foreignid, longitude, latitude, hostid, morton, status) VALUES (IF(NEW.groupType=1,3,4), NEW.id, NEW.geo_long, NEW.geo_lat, NEW.hostid, 0, NEW.Status);
ELSEIF (OLD.geo_lat!=NEW.geo_lat OR OLD.geo_long != NEW.geo_long OR OLD.status != NEW.status) THEN
UPDATE geo SET lat = NEW.geo_lat, long = NEW.geo_long, status = NEW.status WHERE datatype IN (3,4) AND foreignid = NEW.id;
END IF;
END IF;
END IF;
END
So your trigger for table B should look like this:
CREATE TRIGGER `usergroups_comments_insert` AFTER INSERT ON `usergroups_comment`
FOR EACH ROW
BEGIN
IF @TRIGGER_DISABLED = 0 THEN
CALL sp-set-comment_count(NEW.`gid`);
END IF;
END;
If you want the triggers for table A to launch but not to table B, then add the code block only to table B's trigger.
Best Answer
The first method is the best because of the least overhead for the queries generated. In fact, in the MySQL Stored Procedure Language, you want as few declared variables as possible.
The second is not possible since the MySQL Stored Rrocedure Language does not have object support. A clumsy but workable UPDATE JOIN is possible if you...
The third method is not possible because explicit DDL and DDL via Dynamic SQL are not allowed in MySQL Triggers.
You may have to create a regular table using either the MyISAM or MEMORY storage engine. Then, you can have the trigger compile your data to a table that actually exists. MyISAM is better because should a server go down, the compiled data thus far is on disk. MEMORY tables are faster to write to, but are gone on system restart.
DO NOT USE CREATE TEMPORARY TABLE AT ALL because such tables only last as long as the DB connection lives, and would be private unto the call of the trigger also. Even worse off, if you are using MySQL Replication and you run
STOP SLAVE
on the slave, any tables created via CREATE TEMPORARY TABLE disappears from the SQL Thread and replication breaks immediately when you runSTART SLAVE
and those temp tables no longer exist.