Question

At a high level, what I have is this:

  1. A 'master' database that is populated and managed by Application 1.
  2. A 'slave' database on a separate host that is replicated from the 'master' database using the standard mechanism.
  3. Application 2, which runs independently of Application 1 and has very limited read-only access to the slave database and read-write access to its own separate database.

And basically what needs to happen is that when certain things change in the 'slave' database Application 2 needs to be notified so that it can inspect the contents of the 'slave' database and write some stuff into its own database.

Performance is not a concern, and it is acceptable to lock the entire 'slave' database while this happens. The critical thing is to ensure that no further information is replicated into the 'slave' database until Application 2 has done its thing. Application 2 accesses the 'slave' database from a session independent of the one that triggers the notification.

To accomplish this, I have the following trigger:

delimiter //
CREATE TRIGGER create_report_trigger AFTER UPDATE ON jobs
        FOR EACH ROW 
                BEGIN
                        DECLARE report_id INT;
                        IF (NEW.status = 7 AND OLD.status != 7) THEN
                                CALL CREATE_REPORT_PROC(NEW.id, @report_id);
                        END IF;
                END;
        //
delimiter ;

...and since you can't start a transaction or lock the database from within a trigger, I also have the following procedure:

delimiter //
CREATE PROCEDURE create_report_proc( 
        IN jobId INT,
        OUT report_id INT 
)
        BEGIN
                START TRANSACTION WITH CONSISTENT SNAPSHOT;
                SELECT CREATE_REPORT(jobId) INTO report_id;
                COMMIT;
        END //
delimiter ;

The procedure is calling out to a user-defined function which uses libcurl to contact Application 2 and let it know that it needs to process the job.

The procedure works perfectly when I invoke it manually on the MySQL command line. However, when it gets called from the trigger, the following error appears in the MySQL log:

Explicit or implicit commit is not allowed in stored function or trigger

...so apparently MySQL is smart enough to detect that I've attempted to subvert its "no locking from inside of a trigger" rule by having the trigger delegate to a procedure.

The user-defined-function waits for Application 2's operation to complete before returning a result to its caller, which may be relevant (if the MySQL replication process is essentially blocked by the trigger execution, then there's no need to manually lock anything; the replication process is the only thing that is able to make changes to the 'slave' database).

Anyhow, I suppose there are two questions here:

  1. Since the trigger fires as a consequence of an update made by the MySQL replication process, does this mean that the replication process is blocked until the trigger returns?

  2. If not, how do I lock the database or otherwise stop the replication process from the trigger? I suppose issuing a STOP SLAVE; from inside the trigger might do it?

Edit - And here's a bonus followup question:

When Application 2 goes to do its thing, the data that it sees does not reflect the latest information that should be in the 'slave' database. Specifically, any updates made to the jobs table as part of the triggering transaction are not visible to Application 2.

Why is this the case, given that the trigger is configured to fire AFTER UPDATE? And is there any way to make the new content be visible to Application 2 in the database, or is it necessary to manually collect and pass all of the updated field values as part of the notification that gets sent?

Was it helpful?

Solution

Okay, so based upon my own research:

  1. Trigger execution is synchronous, and blocks the calling thread until the trigger completes. MySQL replication is, by default, essentially a single-threaded processes (and I think even if you enable multi-threaded replication you only get one thread per database). So in my case blocking the replication thread "locks" the database for all practical purposes; there's nobody except the replication thread who has write permissions.

  2. You don't, explicitly. However the execution of a trigger is considered to be part of the transaction that triggered it, meaning that you may be implicitly locked/in an atomic transaction anyways. The extent to which this occurs seems to depend upon your storage engine and transaction isolation settings.

  3. Still not entirely sure about the 'bonus' question, but I'd surmise it's related to #2. If the trigger execution is part of the transaction that triggered it, then that transaction cannot commit until after the trigger completes. And if the transaction hasn't committed, then observers on other sessions will not be able to see any of the changes that are part of it.

    In any case, passing the updated fields as parameters to the UDF solved this issue well enough.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top