← Back to team overview

maria-developers team mailing list archive

Re: [Bug 544173] [NEW] Server crash for multi-engine transaction with binlog disabled

 

Paul McCullagh <paul.mccullagh@xxxxxxxxxxxxx> writes:

> On Mar 25, 2010, at 8:39 AM, Kristian Nielsen wrote:

>> Yes. This is simple enough to do with DBUG. Just insert code that
>> makes each
>> engine fail in their prepare() when the appropriate DBUG flag is set.
>>
>> Another test we need is to have similar code to crash the server at
>> the same
>> points. Then on server restart check that the other engine does
>> rollback.
>
> It would be great to have these tests. As Arjen says, this code is not
> well traveled.

An example for how to do this (from a random dive into the source tree) is in
mysql-test/suite/maria/t/maria-recovery.test, which uses
mysql-test/include/maria_verify_recovery.inc to crash the server at specific
point and verify that crash recovery works. It shouldn't be hard to do
something similar for this case (also with just commit fail instead of crash).

Hopefully this could be useful if someone wants to implement such test.

>> (For example current code has no protection against another
>> thransaction
>> seeing a transient state with one engine committed and another not,
>> even using
>> START TRANSACTION WITH CONSISTENT SNAPSHOT. And there are
>> fundamentally
>> unsolvable problems with transactions that span both MVCC- and lock- 
>> based
>> engines).
>
> I would be interested in an actual example of something does not work.
> Right now I have a problem imagining why something would not work.

I just happened to run the following test two days ago:

Consider the following tables

    create table t1 (a int primary key) engine=innodb
    create table t2 (b int primary key) engine=pbxt
    insert into t1 values (1)
    insert into t2 values (1)

I run the following statement repeatedly in one thread:

    UPDATE t1,t2 SET a=a+1,b=b+1

It would be natural to assume that other threads will never be able to see
different values for a and b in a single transaction, but that assumption
would be wrong. I run the following statement repeatedly in a different
thread:

    SELECT a,b FROM t1,t2

After just a few iterations, this will return a row with a=b+1.

(The reason I did this test was that I was looking at the XA multi-engine
code, and not seeing any code to enforce cross-engine consistency. I guess
this test shows there just is no such code ...)

I didn't report it as a bug, as I was not sure if it is a bug or not ... maybe
it is? I'd want a better fix than just taking a global lock over every commit
(which could hurt performance a lot), and such fix may be non-trivial...

Here are the Perl long-liners I used to see this, just run them in parallel to
see the failure:

    perl -MDBI -le 'use strict; my $dbh=DBI->connect("dbi:mysql:database=test", "<user>", "<password>", {RaiseError => 1}); $dbh->do("SET binlog_format=row"); $dbh->do($_) for ("drop table if exists t1,t2", "create table t1 (a int primary key) engine=innodb", "create table t2 (b int primary key) engine=pbxt", "begin", "insert into t1 values (1)", "insert into t2 values (1)", "commit"); for (;;) { $dbh->do("UPDATE t1,t2 SET a=a+1,b=b+1");}'

    perl -MDBI -le 'use strict; my $dbh=DBI->connect("dbi:mysql:database=test", "<user>", "<password>", {RaiseError => 1}); $dbh->do("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ"); for(;;) {my $a=$dbh->selectrow_arrayref("SELECT a,b FROM t1,t2"); print join(" ", @$a); die if $a->[0] != $a->[1];}'

(CONSISTENT SNAPSHOT does not make a difference, as is seen by replacing the
second command with this:

    perl -MDBI -le 'use strict; my $dbh=DBI->connect("dbi:mysql:database=test", "<user>", "<password>", {RaiseError => 1}); $dbh->do("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ"); for(;;) {$dbh->do("START TRANSACTION WITH CONSISTENT SNAPSHOT");my $a=$dbh->selectrow_arrayref("SELECT a,b FROM t1,t2"); print join(" ", @$a); die if $a->[0] != $a->[1];$dbh->do("COMMIT");}'

)

With respect to the fundamental problems with combining MVCC and locking
engines; I know I reported a documentation bug for this long ago, though
google failed to find it for me. And I'm not sure how to repeat it now, as I
don't have any non-mvcc engines easily available (MariaDB has no NDB, and BDB
is gone also). But it goes something like this:

In a locking engine, a transaction sees the (consistent) state of the database
as it is at the *end* of the transactions. So the transaction can see all other
transactions that committed before it did.

In an mvcc engine, a transaction sees the (consistent) state of the database
as it is at the *start* of the transaction. So it sees no transactions that
started after it did.

So to get an inconsistency, something like this should work:

TRN1: SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
TRN1: BEGIN;
TRN1: SELECT * FROM mvcc_table;

                TRN2: BEGIN;
                TRN2: UPDATE mvcc_table SET amount - amount - 100;
                TRN2: UPDATE locking_table SET amount = amount + 100;
                TRN2: COMMIT;

TRN1: SELECT * from locking_table;
TRN1: COMMIT;

In such a case, TRN1 will see the update of the locking_table, but not the
update of the mvcc_table, which gives an inconsistent view of the database. I
don't really see any way to solve this. (Of course one could add LOCK IN
SHARE MODE to every select, in effect turning the mvcc engine into a locking
engine).

 - Kristian.



Follow ups

References