views:

223

answers:

2

Hello,

I'm having what seems to be a concurrency problem while using MySQL and PHP + Propel 1.3. Below is a small example of the "save" method of a Propel object.

public function save(PropelPDO $con = null) {
    $con = Propel::getConnection();
    try {
        $con->beginTransaction();
        sleep(3); // ignore this, used for testing only
        parent::save($con);
        $foo = $this->getFoo(); // Propel object, triggers a SELECT

        // stuff is happening here...

        $foo->save($con);
        $con->commit();
    } catch (Exception $e) {
        $con->rollBack();
        throw $e;
    }
}

The problem is the $foo object. Let's say we get two calls of the example method one after another in a very short time. In some cases, if the second transaction reads $foo...

$foo = $this->getFoo();

... before the first transaction has had the chance to save it...

$foo->save($con);

... $foo read by the second transaction will be outdated and bad things will happen.

How can I force the locking of the table Foo objects are stored in so that subsequent transactions can read from it only after the first one has finished its work?

EDIT: The context is a web application. In short, in some cases I want the very first request to do some data modification (which happens between fetching and saving of $foo). All subsequent requests should not be able to do the modification. Whether the modification will occur or not depends on the fetched $foo state (table row attribute). If two transactions fetch the same $foo, the modification will occur twice which causes a problem.

+1  A: 

when you load this existing row to the screen/application, load the LastChgDate too. when you save it, use "AND LastChgDate=thevalue". check the affected row count of the update, if it is zero, return an error "someone else has already saved this record", and rollback and other changes. With this logic in place you can only save a row if it the same as when you loaded it. for new rows, INSERT, this is not necessary because they are new.

KM
This could work, however, I'm a little reluctant to change the schema right now and since Propel is an ORM, I'd probably have to "bend" it to achieve what you suggested. Before I do that, I'm wondering if there's some "higher level" solution. Surely there must be a way to lock a table (or tables) during a transaction? I have tried 'LOCK TABLE' immediately after starting the transaction, but for some reason it didn't help...
Ree
@Ree, can't you just add a last changed date property, load it with the other properties, etc. you lock the row, but it can be overwritten with JUNK when the lock is released. I like using the last changed date, because it can be useful to the application, you could also use a column of timestamp datatype which would work about the same, but isn't usefull to display, or when the user asks "when was this changed".
KM
I solved the problem by checking that the $foo being updated is the same version that was read. Since I used this answer as the base for the solution, I'm flagging it as the best one.
Ree
What stops someone from writing the row after you've checked the version?
RibaldEddie
That is, in between the time of the check and your decision to write.
RibaldEddie
@RibaldEddie, nothing stops, but I do not care. If another request writes it sooner, the $foo version read will not match the one that is being updated and I will act accordingly in that case.
Ree
@RibaldEddie, check in the actual update, as I suggest "AND LastChgDate=thevalue" in the WHERE and then check row count...
KM
A: 

In MySQL, I think you can use SELECT FOR UPDATE to accomplish the lock.

Another option is to use the GET_LOCK and RELEASE_LOCK MySQL function calls to create named locks that you would use to control access to the resource.

There are some downsides to these approaches. I haven't used them myself very much and they are MySQL specific but they could work for you.

RibaldEddie
you can lock the row, but when you are finished with your update, someone will overwrite your data with stale out of date data!
KM
If you can't get a lock, instead of blocking, just return to the user and tell them that someone else is editing the row and that the data will be redisplayed when the row becomes available and has been updated.
RibaldEddie
You probably need to use threading in that case.
RibaldEddie