Take the 2-minute tour ×
Database Administrators Stack Exchange is a question and answer site for database professionals who wish to improve their database skills and learn from others in the community. It's 100% free, no registration required.

A post here on DBA.StackExchange (What are the best practices for triggers to maintain a revision number on records?) has spawned an interesting question (at least, interesting to me) regarding performance in MySQL.

The context is that we want to insert a record in a table for each row that is updated. Before the row is updated, we want to store off a previous value and then increment one of the columns (a "version" column).

If we do this inside a trigger, it works out nicely. For MySQL, the triggers are row by row, so it would be an easy solution. Select the data currently in the table, insert it into the logging table, and the update the "version" column in the new data.

However, it's possible to move this logic to a stored procedure. If you do that, you're performing the insert, then incrementing the "version" column in the table. The entire thing would be set based.

So, when it comes to performing this insert, would this be more performant to use the set-based stored procedure approach or a trigger based approach?

This question is for MySQL (since it has row-by-row triggers), although it could apply to other row-by-row trigger DBMSs.

share|improve this question
1  
One thing to keep in mind with regard to pushing the versioning logic to a stored procedure - how humped are you going to be when someone, somehow directly writes to the table bypassing your audit mechanism? –  billinkc Aug 14 '11 at 19:42
    
I agree. But at the other end of the scale, maybe you want to intentionally bypass this logging in certain circumstances. Of course, that's an entirely different question. I'm really just curious about the performance implications. –  Richard Aug 15 '11 at 15:25

2 Answers 2

up vote 2 down vote accepted
+50

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.

share|improve this answer

Here is an approach to perform this update in bulk.

For this example

  • table_A has PRIMARY KEY id
  • You create a table called table_A_Keys2Update with just id as PRIMARY KEY
  • You populate table_A_Keys2Update withs ids from table_A you know must be updated

To create table_A_Keys2Update do the following:

CREATE TABLE table_A_Keys2Update SELECT id FROM table_A;
ALTER TABLE table_A_Keys2Update ADD PRIMARY KEY (id);

After you populate table_A_Keys2Update with the ids whose revision number needs to be incremented, perform the following UPDATE JOIN to increment revision number of all rows whose id is in both table_A and table_A_Keys2Update :

UPDATE table_A A INNER JOIN table_A_Keys2Update B USING (id)
SET A.revision = A.revision + 1;

This one line query can replace a trigger and a stored procedure.

Optionally, you can place this one query in a stored procedure and call it if you so desire.

share|improve this answer
    
It's really the Insert I'm curious about. If you INSERT INTO audit SELECT <whatever> FROM <primary_table> WHERE <parameters from stored procedure> you could do the insert in bulk. In the trigger, you would just INSERT INTO audit VALUES <data from the updated row>. So, would the single line, row-by-row insert be quicker than the bulk insert? –  Richard Aug 12 '11 at 18:27
    
For the sake of simplicity, the trigger would be much better 1) provided the primary_table never experiences bulks inserts in the middle of any peak times, 2) audit info needs to be read on-demand at any given moment, and 3) your site is low-trafficked. –  RolandoMySQLDBA Aug 12 '11 at 18:37

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.