Bug #80464 Transactions behavior fail with mysql_real_connect() and CLIENT_MULTI_STATEMENTS
Submitted: 22 Feb 2016 4:24 Modified: 11 May 2017 9:43
Reporter: Charly Batista Email Updates:
Status: Not a Bug Impact on me:
None 
Category:MySQL Server: C API (client library) Severity:S1 (Critical)
Version:5.5+ OS:Any
Assigned to: CPU Architecture:Any

[22 Feb 2016 4:24] Charly Batista
Description:
Using mysql_real_connect() and CLIENT_MULTI_STATEMENTS with transactions has an interesting/odd behavior when one of the operations fail. 

Suppose we execute mysql_query() with a set of 10 inserts to database and one of the inserts fail for any reason. It is supposed to rollback the whole transaction.

If we try to send COMMIT or ROLLBACK immediately after the mysql_query() it will return the error Commands out of sync. 

If we do other operation like loop the result using mysql_next_result() to check the whether there is error or not and after that send COMMIT or ROLLBACK it will execute without error.

The most odd/weird here is that if we execute ROLLBACK it will execute as expected and will rolled back the whole transaction but if we send COMMIT it will commit all the transactions until the first error. For the example, if the 4th operation fails the DB will commit the first 3 operations and rollback the other 7!!  It completely break the concept of atomicity for transactions where or ALL of them COMMIT or ALL of them ROLLBACK.

How to repeat:
-- SQL to create the table
Create Table: CREATE TABLE `test_table` (
  `id` int(11) NOT NULL,
  `name` varchar(50) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB;

#include <mysql.h>
#include <stdio.h>

void main() {
    MYSQL *conn;
    MYSQL_RES *result;
    MYSQL_ROW row;
    char *server = "localhost";
    char *user = "usr_test";
    char *password = "XXX";
    char *database = "test";
    conn = mysql_init(NULL);
    int status = 0;
    
    /* Connect to database */
    if (!mysql_real_connect(conn, server, user, password, database, 0, NULL, CLIENT_MULTI_STATEMENTS)) {
        fprintf(stderr, "[%d]%s\n", __LINE__, mysql_error(conn));
        exit(1);
    }

    /* execute multiple statements */
    status = mysql_query(conn, "BEGIN;");
    if (status) {
        fprintf(stderr, "[%d]%s\n", __LINE__, mysql_error(conn));
        goto clean;
    }
    status = mysql_query(conn,
                         "INSERT INTO test_table VALUES(10, 'ABCD1');\
                          INSERT INTO test_table VALUES(11, 'ABCD2');\
                          INSERT INTO test_table VALUES(12, 'ABCD3');\
                          INSERT INTO test_table VALUES(13, 'ABCD4');\
                          INSERT INTO test_table VALUES(10, 'ABCD5');\
                          INSERT INTO test_table VALUES(16, 'ABCD6');\
                          INSERT INTO test_table VALUES(17, 'ABCD7');\
                          INSERT INTO test_table VALUES(18, 'ABCD8');\
                          INSERT INTO test_table VALUES(19, 'ABCD9');\
                          INSERT INTO test_table VALUES(20, 'ABCD10');\
                          ");
    if (status) {
        fprintf(stderr, "[%d]%s\n", __LINE__, mysql_error(conn));
        goto clean;
    }

    /* process each statement result */
    int ret = 0;
    do {
        if (mysql_field_count(conn) != 0) { /* some error occurred */
            printf("[%d]Could not retrieve result set\n", __LINE__);
            break;
        }

        /* more results? -1 = no, >0 = error, 0 = yes (keep looping) */
        
        ret += (int)mysql_affected_rows(conn);
        printf("[%d]ret: %d\n", __LINE__, ret);
        if ((status = mysql_next_result(conn)) > 0)
            printf("[%d]Could not execute statement if it works \n", __LINE__);
    } while (status == 0);
    
    printf("[%d]Total Ret: %d\n", __LINE__, ret);

    status = mysql_query(conn, "COMMIT;");
    if (status)
        fprintf(stderr, "[%d]%s\n", __LINE__, mysql_error(conn));
    
clean:
    mysql_close(conn);

}

For this example the DB will commit the first 3 operations and rollback the other 7. It was tested with MySQL 5.5 and 5.6. Using either the default configuration and changing the transaction_isolation to READ-COMMITTED and REPEATABLE-READ.
[11 May 2017 9:43] Chiranjeevi Battula
Hello Charly Batista,

Thank you for the bug report.
I couldn't see any odd behavior in provided test case and it is working as per user manual documentation.
User manual : https://dev.mysql.com/doc/refman/5.5/en/c-api-multiple-queries.html

Thank you for your interest in MySQL.

Thanks,
Chiranjeevi.