A fix and a test case for Bug#12713 "Error in a stored function called from
a SELECT doesn't cause ROLLBACK of statem". The idea of the fix is to ensure that we always commit the current statement at the end of dispatch_command(). In order to not issue redundant disc syncs, an optimization of the two-phase commit protocol is implemented to bypass the two phase commit if the transaction is read-only.
This commit is contained in:
parent
48d326612a
commit
acf9b1f346
742
mysql-test/include/commit.inc
Normal file
742
mysql-test/include/commit.inc
Normal file
@ -0,0 +1,742 @@
|
||||
## Bug#12713 (Error in a stored function called from a SELECT doesn't cause
|
||||
## ROLLBACK of statem)
|
||||
|
||||
##
|
||||
## Pre-Requisites :
|
||||
## - $engine_type should be set
|
||||
##
|
||||
|
||||
set sql_mode=no_engine_substitution;
|
||||
eval set storage_engine = $engine_type;
|
||||
set autocommit=1;
|
||||
|
||||
--disable_warnings
|
||||
drop table if exists t1;
|
||||
drop table if exists t2;
|
||||
drop table if exists t3;
|
||||
drop function if exists f2;
|
||||
drop procedure if exists bug12713_call;
|
||||
drop procedure if exists bug12713_dump_spvars;
|
||||
drop procedure if exists dummy;
|
||||
--enable_warnings
|
||||
|
||||
create table t1 (a int);
|
||||
create table t2 (a int unique);
|
||||
create table t3 (a int);
|
||||
|
||||
# a workaround for Bug#32633: Can not create any routine if
|
||||
# SQL_MODE=no_engine_substitution
|
||||
|
||||
set sql_mode=default;
|
||||
|
||||
insert into t1 (a) values (1), (2);
|
||||
insert into t3 (a) values (1), (2);
|
||||
|
||||
delimiter |;
|
||||
|
||||
## Cause a failure every time
|
||||
create function f2(x int) returns int
|
||||
begin
|
||||
insert into t2 (a) values (x);
|
||||
insert into t2 (a) values (x);
|
||||
return x;
|
||||
end|
|
||||
|
||||
delimiter ;|
|
||||
|
||||
set autocommit=0;
|
||||
|
||||
flush status;
|
||||
##============================================================================
|
||||
## Design notes
|
||||
##
|
||||
## In each case, statement rollback is expected.
|
||||
## for transactional engines, the rollback should be properly executed
|
||||
## for non transactional engines, the rollback may cause warnings.
|
||||
##
|
||||
## The test pattern is as follows
|
||||
## - insert 1000+N
|
||||
## - statement with a side effect, that fails to insert N twice
|
||||
## - a statement rollback is expected (expecting 1 row 1000+N only) in t2
|
||||
## - a rollback is performed
|
||||
## - expecting a clean table t2.
|
||||
##============================================================================
|
||||
|
||||
insert into t2 (a) values (1001);
|
||||
--error ER_DUP_ENTRY
|
||||
insert into t1 (a) values (f2(1));
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1002);
|
||||
--error ER_DUP_ENTRY
|
||||
insert into t3 (a) select f2(2) from t1;
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1003);
|
||||
--error ER_DUP_ENTRY
|
||||
update t1 set a= a + f2(3);
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1004);
|
||||
--error ER_DUP_ENTRY
|
||||
update t1, t3 set t1.a = 0, t3.a = 0 where (f2(4) = 4) and (t1.a = t3.a);
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1005);
|
||||
--error ER_DUP_ENTRY
|
||||
delete from t1 where (a = f2(5));
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1006);
|
||||
--error ER_DUP_ENTRY
|
||||
delete from t1, t3 using t1, t3 where (f2(6) = 6) ;
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1007);
|
||||
--error ER_DUP_ENTRY
|
||||
replace t1 values (f2(7));
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1008);
|
||||
--error ER_DUP_ENTRY
|
||||
replace into t3 (a) select f2(8) from t1;
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1009);
|
||||
--error ER_DUP_ENTRY
|
||||
select f2(9) from t1 ;
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1010);
|
||||
--error ER_DUP_ENTRY
|
||||
show databases where (f2(10) = 10);
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1011);
|
||||
--error ER_DUP_ENTRY
|
||||
show tables where (f2(11) = 11);
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1012);
|
||||
--error ER_DUP_ENTRY
|
||||
show triggers where (f2(12) = 12);
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1013);
|
||||
--error ER_DUP_ENTRY
|
||||
show table status where (f2(13) = 13);
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1014);
|
||||
--error ER_DUP_ENTRY
|
||||
show open tables where (f2(14) = 14);
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1015);
|
||||
--error ER_DUP_ENTRY
|
||||
show columns in mysql.proc where (f2(15) = 15);
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1016);
|
||||
--error ER_DUP_ENTRY
|
||||
show status where (f2(16) = 16);
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1017);
|
||||
--error ER_DUP_ENTRY
|
||||
show variables where (f2(17) = 17);
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1018);
|
||||
--error ER_DUP_ENTRY
|
||||
show charset where (f2(18) = 18);
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1019);
|
||||
--error ER_DUP_ENTRY
|
||||
show collation where (f2(19) = 19);
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
--echo # We need at least one procedure to make sure the WHERE clause is
|
||||
--echo # evaluated
|
||||
create procedure dummy() begin end;
|
||||
insert into t2 (a) values (1020);
|
||||
--error ER_DUP_ENTRY
|
||||
show procedure status where (f2(20) = 20);
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
drop procedure dummy;
|
||||
|
||||
insert into t2 (a) values (1021);
|
||||
--error ER_DUP_ENTRY
|
||||
show function status where (f2(21) = 21);
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1022);
|
||||
prepare stmt from "insert into t1 (a) values (f2(22))";
|
||||
--error ER_DUP_ENTRY
|
||||
execute stmt;
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
insert into t2 (a) values (1023);
|
||||
do (f2(23));
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
## Please note :
|
||||
## This will insert a record 1024 in t1 (statement commit)
|
||||
## This will insert a record 24 in t1 (statement commit)
|
||||
## then will rollback the second insert only (24) (statement rollback)
|
||||
## then will rollback the complete transaction (transaction rollback)
|
||||
|
||||
delimiter |;
|
||||
|
||||
create procedure bug12713_call ()
|
||||
begin
|
||||
insert into t2 (a) values (24);
|
||||
insert into t2 (a) values (24);
|
||||
end|
|
||||
|
||||
delimiter ;|
|
||||
|
||||
insert into t2 (a) values (1024);
|
||||
--error ER_DUP_ENTRY
|
||||
call bug12713_call();
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
--echo =======================================================================
|
||||
--echo Testing select_to_file
|
||||
--echo =======================================================================
|
||||
|
||||
insert into t2 (a) values (1025);
|
||||
|
||||
--replace_result $MYSQLTEST_VARDIR ..
|
||||
--error ER_DUP_ENTRY
|
||||
eval select f2(25) into outfile "$MYSQLTEST_VARDIR/tmp/dml.out" from t1;
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
--remove_file $MYSQLTEST_VARDIR/tmp/dml.out
|
||||
|
||||
insert into t2 (a) values (1026);
|
||||
--replace_result $MYSQLTEST_VARDIR ..
|
||||
--error ER_DUP_ENTRY
|
||||
eval load data infile "../std_data_ln/words.dat" into table t1 (a) set a:=f2(26);
|
||||
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
--echo =======================================================================
|
||||
--echo Testing select_dumpvar
|
||||
--echo =======================================================================
|
||||
|
||||
insert into t2 (a) values (1027);
|
||||
--error ER_DUP_ENTRY
|
||||
select f2(27) into @foo;
|
||||
select * from t2;
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
--echo =======================================================================
|
||||
--echo Testing Select_fetch_into_spvars
|
||||
--echo =======================================================================
|
||||
|
||||
delimiter |;
|
||||
|
||||
create procedure bug12713_dump_spvars ()
|
||||
begin
|
||||
declare foo int;
|
||||
|
||||
declare continue handler for sqlexception
|
||||
begin
|
||||
select "Exception trapped";
|
||||
end;
|
||||
|
||||
select f2(28) into foo;
|
||||
select * from t2;
|
||||
end|
|
||||
|
||||
delimiter ;|
|
||||
|
||||
insert into t2 (a) values (1028);
|
||||
call bug12713_dump_spvars ();
|
||||
rollback;
|
||||
select * from t2;
|
||||
|
||||
--echo =======================================================================
|
||||
--echo Cleanup
|
||||
--echo =======================================================================
|
||||
|
||||
set autocommit=default;
|
||||
|
||||
drop table t1;
|
||||
drop table t2;
|
||||
drop table t3;
|
||||
drop function f2;
|
||||
drop procedure bug12713_call;
|
||||
drop procedure bug12713_dump_spvars;
|
||||
--echo #
|
||||
--echo # Bug#12713 Error in a stored function called from a SELECT doesn't
|
||||
--echo # cause ROLLBACK of statem
|
||||
--echo #
|
||||
--echo # Verify that two-phase commit is not issued for read-only
|
||||
--echo # transactions.
|
||||
--echo #
|
||||
--echo # Verify that two-phase commit is issued for read-write transactions,
|
||||
--echo # even if the change is done inside a stored function called from
|
||||
--echo # SELECT or SHOW statement.
|
||||
--echo #
|
||||
set autocommit=0;
|
||||
--disable_warnings
|
||||
drop table if exists t1;
|
||||
drop table if exists t2;
|
||||
drop function if exists f1;
|
||||
drop procedure if exists p_verify_status_increment;
|
||||
--enable_warnings
|
||||
|
||||
set sql_mode=no_engine_substitution;
|
||||
create table t1 (a int unique);
|
||||
create table t2 (a int) engine=myisam;
|
||||
set sql_mode=default;
|
||||
--echo #
|
||||
--echo # An auxiliary procedure to track Handler_prepare and Handler_commit
|
||||
--echo # statistics.
|
||||
--echo #
|
||||
delimiter |;
|
||||
create procedure
|
||||
p_verify_status_increment(commit_inc_mixed int, prepare_inc_mixed int,
|
||||
commit_inc_row int, prepare_inc_row int)
|
||||
begin
|
||||
declare commit_inc int;
|
||||
declare prepare_inc int;
|
||||
declare old_commit_count int default ifnull(@commit_count, 0);
|
||||
declare old_prepare_count int default ifnull(@prepare_count, 0);
|
||||
declare c_res int;
|
||||
# Use a cursor to have just one access to I_S instead of 2, it is very slow
|
||||
# and amounts for over 90% of test CPU time
|
||||
declare c cursor for
|
||||
select variable_value
|
||||
from information_schema.session_status
|
||||
where variable_name='Handler_commit' or variable_name='Handler_prepare'
|
||||
order by variable_name;
|
||||
|
||||
if @@global.binlog_format = 'ROW' then
|
||||
set commit_inc= commit_inc_row;
|
||||
set prepare_inc= prepare_inc_row;
|
||||
else
|
||||
set commit_inc= commit_inc_mixed;
|
||||
set prepare_inc= prepare_inc_mixed;
|
||||
end if;
|
||||
|
||||
open c;
|
||||
fetch c into c_res;
|
||||
set @commit_count=c_res;
|
||||
fetch c into c_res;
|
||||
set @prepare_count=c_res;
|
||||
close c;
|
||||
|
||||
if old_commit_count + commit_inc <> @commit_count then
|
||||
select concat("Expected commit increment: ", commit_inc,
|
||||
" actual: ", @commit_count - old_commit_count)
|
||||
as 'ERROR';
|
||||
elseif old_prepare_count + prepare_inc <> @prepare_count then
|
||||
select concat("Expected prepare increment: ", prepare_inc,
|
||||
" actual: ", @prepare_count - old_prepare_count)
|
||||
as 'ERROR';
|
||||
else
|
||||
select '' as 'SUCCESS';
|
||||
end if;
|
||||
end|
|
||||
delimiter ;|
|
||||
--echo # Reset Handler_commit and Handler_prepare counters
|
||||
flush status;
|
||||
--echo #
|
||||
--echo # 1. Read-only statement: SELECT
|
||||
--echo #
|
||||
select * from t1;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
|
||||
--echo # 2. Read-write statement: INSERT, insert 1 row.
|
||||
--echo #
|
||||
insert into t1 (a) values (1);
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
|
||||
--echo # 3. Read-write statement: UPDATE, update 1 row.
|
||||
--echo #
|
||||
update t1 set a=2;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
|
||||
--echo # 4. Read-write statement: UPDATE, update 0 rows, 1 row matches WHERE
|
||||
--echo #
|
||||
--echo # Note the wrong Handler_prepare/Handler_commit count is due to
|
||||
--echo # Bug#29157 "UPDATE, changed rows incorrect" and
|
||||
--echo # Bug#Bug #33846 UPDATE word:Wrong 'Changed rows' if InnoDB, unique
|
||||
--echo # key and no rows qualify WHERE
|
||||
--echo #
|
||||
update t1 set a=2;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
|
||||
--echo # 5. Read-write statement: UPDATE, update 0 rows, 0 rows match WHERE
|
||||
--echo #
|
||||
--echo # In mixed replication mode, there is a read-only transaction
|
||||
--echo # in InnoDB and also the statement is written to the binary log.
|
||||
--echo # So we have two commits but no 2pc, since the first engine's
|
||||
--echo # transaction is read-only.
|
||||
--echo # In the row level replication mode, we only have the read-only
|
||||
--echo # transaction in InnoDB and nothing is written to the binary log.
|
||||
--echo #
|
||||
update t1 set a=3 where a=1;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
|
||||
--echo # 6. Read-write statement: DELETE, delete 0 rows.
|
||||
--echo #
|
||||
delete from t1 where a=1;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
|
||||
--echo # 7. Read-write statement: DELETE, delete 1 row.
|
||||
--echo #
|
||||
delete from t1 where a=2;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
|
||||
--echo # 8. Read-write statement: unqualified DELETE
|
||||
--echo #
|
||||
--echo # In statement or mixed replication mode, we call
|
||||
--echo # handler::ha_delete_all_rows() and write statement text
|
||||
--echo # to the binary log. This results in two read-write transactions.
|
||||
--echo # In row level replication mode, we do not call
|
||||
--echo # handler::ha_delete_all_rows(), but delete rows one by one.
|
||||
--echo # Since there are no rows, nothing is written to the binary log.
|
||||
--echo # Thus we have just one read-only transaction in InnoDB.
|
||||
delete from t1;
|
||||
call p_verify_status_increment(2, 2, 1, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 1, 0);
|
||||
|
||||
--echo # 9. Read-write statement: REPLACE, change 1 row.
|
||||
--echo #
|
||||
replace t1 set a=1;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
|
||||
--echo # 10. Read-write statement: REPLACE, change 0 rows.
|
||||
--echo #
|
||||
replace t1 set a=1;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
|
||||
--echo # 11. Read-write statement: IODKU, change 1 row.
|
||||
--echo #
|
||||
insert t1 set a=1 on duplicate key update a=a+1;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
select * from t1;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
|
||||
--echo # 12. Read-write statement: IODKU, change 0 rows.
|
||||
--echo #
|
||||
insert t1 set a=2 on duplicate key update a=2;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
|
||||
--echo # 13. Read-write statement: INSERT IGNORE, change 0 rows.
|
||||
--echo #
|
||||
insert ignore t1 set a=2;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
|
||||
--echo # 14. Read-write statement: INSERT IGNORE, change 1 row.
|
||||
--echo #
|
||||
insert ignore t1 set a=1;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
--echo # 15. Read-write statement: UPDATE IGNORE, change 0 rows.
|
||||
--echo #
|
||||
update ignore t1 set a=2 where a=1;
|
||||
call p_verify_status_increment(2, 2, 1, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 1, 0);
|
||||
--echo #
|
||||
--echo # Create a stored function that modifies a
|
||||
--echo # non-transactional table. Demonstrate that changes in
|
||||
--echo # non-transactional tables do not affect the two phase commit
|
||||
--echo # algorithm.
|
||||
--echo #
|
||||
delimiter |;
|
||||
create function f1() returns int
|
||||
begin
|
||||
insert t2 set a=2;
|
||||
return 2;
|
||||
end|
|
||||
delimiter ;|
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
|
||||
--echo # 16. A function changes non-trans-table.
|
||||
--echo #
|
||||
select f1();
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
|
||||
--echo # 17. Read-only statement, a function changes non-trans-table.
|
||||
--echo #
|
||||
select f1() from t1;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
|
||||
--echo # 18. Read-write statement: UPDATE, change 0 (transactional) rows.
|
||||
--echo #
|
||||
select count(*) from t2;
|
||||
update t1 set a=2 where a=f1()+10;
|
||||
select count(*) from t2;
|
||||
call p_verify_status_increment(2, 0, 2, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 0, 2, 0);
|
||||
--echo #
|
||||
--echo # Replace the non-transactional table with a temporary
|
||||
--echo # transactional table. Demonstrate that a change to a temporary
|
||||
--echo # transactional table does not provoke 2-phase commit, although
|
||||
--echo # does trigger a commit and a binlog write (in statement mode).
|
||||
--echo #
|
||||
drop table t2;
|
||||
set sql_mode=no_engine_substitution;
|
||||
create temporary table t2 (a int);
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
set sql_mode=default;
|
||||
--echo # 19. A function changes temp-trans-table.
|
||||
--echo #
|
||||
select f1();
|
||||
--echo # Two commits because a binary log record is written
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
|
||||
--echo # 20. Read-only statement, a function changes non-trans-table.
|
||||
--echo #
|
||||
select f1() from t1;
|
||||
--echo # Two commits because a binary log record is written
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
|
||||
--echo # 21. Read-write statement: UPDATE, change 0 (transactional) rows.
|
||||
--echo #
|
||||
update t1 set a=2 where a=f1()+10;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
|
||||
--echo # 22. DDL: ALTER TEMPORARY TABLE, should not cause a 2pc
|
||||
--echo #
|
||||
alter table t2 add column b int default 5;
|
||||
--echo # A commit is done internally by ALTER.
|
||||
call p_verify_status_increment(2, 0, 2, 0);
|
||||
commit;
|
||||
--echo # There is nothing left to commit
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
|
||||
--echo # 23. DDL: RENAME TEMPORARY TABLE, does not start a transaction
|
||||
--echo
|
||||
--echo # No test because of Bug#8729 "rename table fails on temporary table"
|
||||
|
||||
--echo # 24. DDL: TRUNCATE TEMPORARY TABLE, does not start a transaction
|
||||
--echo
|
||||
truncate table t2;
|
||||
call p_verify_status_increment(2, 0, 2, 0);
|
||||
commit;
|
||||
--echo # There is nothing left to commit
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
|
||||
--echo # 25. Read-write statement: unqualified DELETE
|
||||
--echo
|
||||
delete from t2;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
commit;
|
||||
--echo # There is nothing left to commit
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
|
||||
--echo # 25. DDL: DROP TEMPORARY TABLE, does not start a transaction
|
||||
--echo #
|
||||
drop temporary table t2;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
|
||||
--echo # 26. Verify that SET AUTOCOMMIT issues an implicit commit
|
||||
--echo #
|
||||
insert t1 set a=3;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
set autocommit=1;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
rollback;
|
||||
select a from t1 where a=3;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
delete from t1 where a=3;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
commit;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
set autocommit=0;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
insert t1 set a=3;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
--echo # Sic: not actually changing the value of autocommit
|
||||
set autocommit=0;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
rollback;
|
||||
select a from t1 where a=3;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
|
||||
--echo # 27. Savepoint management
|
||||
--echo #
|
||||
insert t1 set a=3;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
savepoint a;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
insert t1 set a=4;
|
||||
--echo # Sic: a bug. Binlog did not register itself this time.
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
release savepoint a;
|
||||
rollback;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
select a from t1 where a=3;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
|
||||
--echo # 28. Read-write statement: DO
|
||||
--echo #
|
||||
create table t2 (a int);
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
do (select f1() from t1 where a=2);
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
|
||||
--echo # 29. Read-write statement: MULTI-DELETE
|
||||
--echo #
|
||||
delete t1, t2 from t1 join t2 on (t1.a=t2.a) where t1.a=2;
|
||||
commit;
|
||||
call p_verify_status_increment(4, 4, 4, 4);
|
||||
|
||||
--echo # 30. Read-write statement: INSERT-SELECT, MULTI-UPDATE, REPLACE-SELECT
|
||||
--echo #
|
||||
insert into t2 select a from t1;
|
||||
commit;
|
||||
replace into t2 select a from t1;
|
||||
commit;
|
||||
call p_verify_status_increment(8, 8, 8, 8);
|
||||
#
|
||||
# Multi-update is one of the few remaining statements that still
|
||||
# locks the tables at prepare step (and hence starts the transaction.
|
||||
# Disable the PS protocol, since in this protocol we get a different
|
||||
# number of commmits (there is an extra commit after prepare
|
||||
#
|
||||
--disable_ps_protocol
|
||||
update t1, t2 set t1.a=4, t2.a=8 where t1.a=t2.a and t1.a=1;
|
||||
--enable_ps_protocol
|
||||
commit;
|
||||
call p_verify_status_increment(4, 4, 4, 4);
|
||||
|
||||
--echo # 31. DDL: various DDL with transactional tables
|
||||
--echo #
|
||||
--echo # Sic: no table is created.
|
||||
create table if not exists t2 (a int) select 6 union select 7;
|
||||
--echo # Sic: first commits the statement, and then the transaction.
|
||||
call p_verify_status_increment(4, 4, 4, 4);
|
||||
create table t3 select a from t2;
|
||||
call p_verify_status_increment(4, 4, 4, 4);
|
||||
alter table t3 add column (b int);
|
||||
call p_verify_status_increment(2, 0, 2, 0);
|
||||
alter table t3 rename t4;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
rename table t4 to t3;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
truncate table t3;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
create view v1 as select * from t2;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
check table t1;
|
||||
call p_verify_status_increment(3, 0, 3, 0);
|
||||
--echo # Sic: after this bug is fixed, CHECK leaves no pending transaction
|
||||
commit;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
check table t1, t2, t3;
|
||||
call p_verify_status_increment(6, 0, 6, 0);
|
||||
commit;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
drop view v1;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
|
||||
--echo #
|
||||
--echo # Cleanup
|
||||
--echo #
|
||||
drop table t1;
|
||||
drop procedure p_verify_status_increment;
|
||||
drop function f1;
|
888
mysql-test/r/commit_1innodb.result
Normal file
888
mysql-test/r/commit_1innodb.result
Normal file
@ -0,0 +1,888 @@
|
||||
set sql_mode=no_engine_substitution;
|
||||
set storage_engine = InnoDB;
|
||||
set autocommit=1;
|
||||
drop table if exists t1;
|
||||
drop table if exists t2;
|
||||
drop table if exists t3;
|
||||
drop function if exists f2;
|
||||
drop procedure if exists bug12713_call;
|
||||
drop procedure if exists bug12713_dump_spvars;
|
||||
drop procedure if exists dummy;
|
||||
create table t1 (a int);
|
||||
create table t2 (a int unique);
|
||||
create table t3 (a int);
|
||||
set sql_mode=default;
|
||||
insert into t1 (a) values (1), (2);
|
||||
insert into t3 (a) values (1), (2);
|
||||
create function f2(x int) returns int
|
||||
begin
|
||||
insert into t2 (a) values (x);
|
||||
insert into t2 (a) values (x);
|
||||
return x;
|
||||
end|
|
||||
set autocommit=0;
|
||||
flush status;
|
||||
insert into t2 (a) values (1001);
|
||||
insert into t1 (a) values (f2(1));
|
||||
ERROR 23000: Duplicate entry '1' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1001
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1002);
|
||||
insert into t3 (a) select f2(2) from t1;
|
||||
ERROR 23000: Duplicate entry '2' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1002
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1003);
|
||||
update t1 set a= a + f2(3);
|
||||
ERROR 23000: Duplicate entry '3' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1003
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1004);
|
||||
update t1, t3 set t1.a = 0, t3.a = 0 where (f2(4) = 4) and (t1.a = t3.a);
|
||||
ERROR 23000: Duplicate entry '4' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1004
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1005);
|
||||
delete from t1 where (a = f2(5));
|
||||
ERROR 23000: Duplicate entry '5' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1005
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1006);
|
||||
delete from t1, t3 using t1, t3 where (f2(6) = 6) ;
|
||||
ERROR 23000: Duplicate entry '6' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1006
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1007);
|
||||
replace t1 values (f2(7));
|
||||
ERROR 23000: Duplicate entry '7' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1007
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1008);
|
||||
replace into t3 (a) select f2(8) from t1;
|
||||
ERROR 23000: Duplicate entry '8' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1008
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1009);
|
||||
select f2(9) from t1 ;
|
||||
ERROR 23000: Duplicate entry '9' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1009
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1010);
|
||||
show databases where (f2(10) = 10);
|
||||
ERROR 23000: Duplicate entry '10' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1010
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1011);
|
||||
show tables where (f2(11) = 11);
|
||||
ERROR 23000: Duplicate entry '11' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1011
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1012);
|
||||
show triggers where (f2(12) = 12);
|
||||
ERROR 23000: Duplicate entry '12' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1012
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1013);
|
||||
show table status where (f2(13) = 13);
|
||||
ERROR 23000: Duplicate entry '13' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1013
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1014);
|
||||
show open tables where (f2(14) = 14);
|
||||
ERROR 23000: Duplicate entry '14' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1014
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1015);
|
||||
show columns in mysql.proc where (f2(15) = 15);
|
||||
ERROR 23000: Duplicate entry '15' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1015
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1016);
|
||||
show status where (f2(16) = 16);
|
||||
ERROR 23000: Duplicate entry '16' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1016
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1017);
|
||||
show variables where (f2(17) = 17);
|
||||
ERROR 23000: Duplicate entry '17' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1017
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1018);
|
||||
show charset where (f2(18) = 18);
|
||||
ERROR 23000: Duplicate entry '18' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1018
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1019);
|
||||
show collation where (f2(19) = 19);
|
||||
ERROR 23000: Duplicate entry '19' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1019
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
# We need at least one procedure to make sure the WHERE clause is
|
||||
# evaluated
|
||||
create procedure dummy() begin end;
|
||||
insert into t2 (a) values (1020);
|
||||
show procedure status where (f2(20) = 20);
|
||||
ERROR 23000: Duplicate entry '20' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1020
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
drop procedure dummy;
|
||||
insert into t2 (a) values (1021);
|
||||
show function status where (f2(21) = 21);
|
||||
ERROR 23000: Duplicate entry '21' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1021
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1022);
|
||||
prepare stmt from "insert into t1 (a) values (f2(22))";
|
||||
execute stmt;
|
||||
ERROR 23000: Duplicate entry '22' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1022
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1023);
|
||||
do (f2(23));
|
||||
Warnings:
|
||||
Error 1062 Duplicate entry '23' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1023
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
create procedure bug12713_call ()
|
||||
begin
|
||||
insert into t2 (a) values (24);
|
||||
insert into t2 (a) values (24);
|
||||
end|
|
||||
insert into t2 (a) values (1024);
|
||||
call bug12713_call();
|
||||
ERROR 23000: Duplicate entry '24' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
24
|
||||
1024
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
=======================================================================
|
||||
Testing select_to_file
|
||||
=======================================================================
|
||||
insert into t2 (a) values (1025);
|
||||
select f2(25) into outfile "../tmp/dml.out" from t1;
|
||||
ERROR 23000: Duplicate entry '25' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1025
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
insert into t2 (a) values (1026);
|
||||
load data infile "../std_data_ln/words.dat" into table t1 (a) set a:=f2(26);
|
||||
ERROR 23000: Duplicate entry '26' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1026
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
=======================================================================
|
||||
Testing select_dumpvar
|
||||
=======================================================================
|
||||
insert into t2 (a) values (1027);
|
||||
select f2(27) into @foo;
|
||||
ERROR 23000: Duplicate entry '27' for key 'a'
|
||||
select * from t2;
|
||||
a
|
||||
1027
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
=======================================================================
|
||||
Testing Select_fetch_into_spvars
|
||||
=======================================================================
|
||||
create procedure bug12713_dump_spvars ()
|
||||
begin
|
||||
declare foo int;
|
||||
declare continue handler for sqlexception
|
||||
begin
|
||||
select "Exception trapped";
|
||||
end;
|
||||
select f2(28) into foo;
|
||||
select * from t2;
|
||||
end|
|
||||
insert into t2 (a) values (1028);
|
||||
call bug12713_dump_spvars ();
|
||||
Exception trapped
|
||||
Exception trapped
|
||||
a
|
||||
1028
|
||||
rollback;
|
||||
select * from t2;
|
||||
a
|
||||
=======================================================================
|
||||
Cleanup
|
||||
=======================================================================
|
||||
set autocommit=default;
|
||||
drop table t1;
|
||||
drop table t2;
|
||||
drop table t3;
|
||||
drop function f2;
|
||||
drop procedure bug12713_call;
|
||||
drop procedure bug12713_dump_spvars;
|
||||
#
|
||||
# Bug#12713 Error in a stored function called from a SELECT doesn't
|
||||
# cause ROLLBACK of statem
|
||||
#
|
||||
# Verify that two-phase commit is not issued for read-only
|
||||
# transactions.
|
||||
#
|
||||
# Verify that two-phase commit is issued for read-write transactions,
|
||||
# even if the change is done inside a stored function called from
|
||||
# SELECT or SHOW statement.
|
||||
#
|
||||
set autocommit=0;
|
||||
drop table if exists t1;
|
||||
drop table if exists t2;
|
||||
drop function if exists f1;
|
||||
drop procedure if exists p_verify_status_increment;
|
||||
set sql_mode=no_engine_substitution;
|
||||
create table t1 (a int unique);
|
||||
create table t2 (a int) engine=myisam;
|
||||
set sql_mode=default;
|
||||
#
|
||||
# An auxiliary procedure to track Handler_prepare and Handler_commit
|
||||
# statistics.
|
||||
#
|
||||
create procedure
|
||||
p_verify_status_increment(commit_inc_mixed int, prepare_inc_mixed int,
|
||||
commit_inc_row int, prepare_inc_row int)
|
||||
begin
|
||||
declare commit_inc int;
|
||||
declare prepare_inc int;
|
||||
declare old_commit_count int default ifnull(@commit_count, 0);
|
||||
declare old_prepare_count int default ifnull(@prepare_count, 0);
|
||||
declare c_res int;
|
||||
# Use a cursor to have just one access to I_S instead of 2, it is very slow
|
||||
# and amounts for over 90% of test CPU time
|
||||
declare c cursor for
|
||||
select variable_value
|
||||
from information_schema.session_status
|
||||
where variable_name='Handler_commit' or variable_name='Handler_prepare'
|
||||
order by variable_name;
|
||||
if @@global.binlog_format = 'ROW' then
|
||||
set commit_inc= commit_inc_row;
|
||||
set prepare_inc= prepare_inc_row;
|
||||
else
|
||||
set commit_inc= commit_inc_mixed;
|
||||
set prepare_inc= prepare_inc_mixed;
|
||||
end if;
|
||||
open c;
|
||||
fetch c into c_res;
|
||||
set @commit_count=c_res;
|
||||
fetch c into c_res;
|
||||
set @prepare_count=c_res;
|
||||
close c;
|
||||
if old_commit_count + commit_inc <> @commit_count then
|
||||
select concat("Expected commit increment: ", commit_inc,
|
||||
" actual: ", @commit_count - old_commit_count)
|
||||
as 'ERROR';
|
||||
elseif old_prepare_count + prepare_inc <> @prepare_count then
|
||||
select concat("Expected prepare increment: ", prepare_inc,
|
||||
" actual: ", @prepare_count - old_prepare_count)
|
||||
as 'ERROR';
|
||||
else
|
||||
select '' as 'SUCCESS';
|
||||
end if;
|
||||
end|
|
||||
# Reset Handler_commit and Handler_prepare counters
|
||||
flush status;
|
||||
#
|
||||
# 1. Read-only statement: SELECT
|
||||
#
|
||||
select * from t1;
|
||||
a
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
# 2. Read-write statement: INSERT, insert 1 row.
|
||||
#
|
||||
insert into t1 (a) values (1);
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
# 3. Read-write statement: UPDATE, update 1 row.
|
||||
#
|
||||
update t1 set a=2;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
# 4. Read-write statement: UPDATE, update 0 rows, 1 row matches WHERE
|
||||
#
|
||||
# Note the wrong Handler_prepare/Handler_commit count is due to
|
||||
# Bug#29157 "UPDATE, changed rows incorrect" and
|
||||
# Bug#Bug #33846 UPDATE word:Wrong 'Changed rows' if InnoDB, unique
|
||||
# key and no rows qualify WHERE
|
||||
#
|
||||
update t1 set a=2;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
# 5. Read-write statement: UPDATE, update 0 rows, 0 rows match WHERE
|
||||
#
|
||||
# In mixed replication mode, there is a read-only transaction
|
||||
# in InnoDB and also the statement is written to the binary log.
|
||||
# So we have two commits but no 2pc, since the first engine's
|
||||
# transaction is read-only.
|
||||
# In the row level replication mode, we only have the read-only
|
||||
# transaction in InnoDB and nothing is written to the binary log.
|
||||
#
|
||||
update t1 set a=3 where a=1;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
# 6. Read-write statement: DELETE, delete 0 rows.
|
||||
#
|
||||
delete from t1 where a=1;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
# 7. Read-write statement: DELETE, delete 1 row.
|
||||
#
|
||||
delete from t1 where a=2;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
# 8. Read-write statement: unqualified DELETE
|
||||
#
|
||||
# In statement or mixed replication mode, we call
|
||||
# handler::ha_delete_all_rows() and write statement text
|
||||
# to the binary log. This results in two read-write transactions.
|
||||
# In row level replication mode, we do not call
|
||||
# handler::ha_delete_all_rows(), but delete rows one by one.
|
||||
# Since there are no rows, nothing is written to the binary log.
|
||||
# Thus we have just one read-only transaction in InnoDB.
|
||||
delete from t1;
|
||||
call p_verify_status_increment(2, 2, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
# 9. Read-write statement: REPLACE, change 1 row.
|
||||
#
|
||||
replace t1 set a=1;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
# 10. Read-write statement: REPLACE, change 0 rows.
|
||||
#
|
||||
replace t1 set a=1;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
# 11. Read-write statement: IODKU, change 1 row.
|
||||
#
|
||||
insert t1 set a=1 on duplicate key update a=a+1;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
select * from t1;
|
||||
a
|
||||
2
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
# 12. Read-write statement: IODKU, change 0 rows.
|
||||
#
|
||||
insert t1 set a=2 on duplicate key update a=2;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
# 13. Read-write statement: INSERT IGNORE, change 0 rows.
|
||||
#
|
||||
insert ignore t1 set a=2;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
# 14. Read-write statement: INSERT IGNORE, change 1 row.
|
||||
#
|
||||
insert ignore t1 set a=1;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
# 15. Read-write statement: UPDATE IGNORE, change 0 rows.
|
||||
#
|
||||
update ignore t1 set a=2 where a=1;
|
||||
call p_verify_status_increment(2, 2, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
#
|
||||
# Create a stored function that modifies a
|
||||
# non-transactional table. Demonstrate that changes in
|
||||
# non-transactional tables do not affect the two phase commit
|
||||
# algorithm.
|
||||
#
|
||||
create function f1() returns int
|
||||
begin
|
||||
insert t2 set a=2;
|
||||
return 2;
|
||||
end|
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
# 16. A function changes non-trans-table.
|
||||
#
|
||||
select f1();
|
||||
f1()
|
||||
2
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
# 17. Read-only statement, a function changes non-trans-table.
|
||||
#
|
||||
select f1() from t1;
|
||||
f1()
|
||||
2
|
||||
2
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
# 18. Read-write statement: UPDATE, change 0 (transactional) rows.
|
||||
#
|
||||
select count(*) from t2;
|
||||
count(*)
|
||||
3
|
||||
update t1 set a=2 where a=f1()+10;
|
||||
select count(*) from t2;
|
||||
count(*)
|
||||
5
|
||||
call p_verify_status_increment(2, 0, 2, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 0, 2, 0);
|
||||
SUCCESS
|
||||
|
||||
#
|
||||
# Replace the non-transactional table with a temporary
|
||||
# transactional table. Demonstrate that a change to a temporary
|
||||
# transactional table does not provoke 2-phase commit, although
|
||||
# does trigger a commit and a binlog write (in statement mode).
|
||||
#
|
||||
drop table t2;
|
||||
set sql_mode=no_engine_substitution;
|
||||
create temporary table t2 (a int);
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
set sql_mode=default;
|
||||
# 19. A function changes temp-trans-table.
|
||||
#
|
||||
select f1();
|
||||
f1()
|
||||
2
|
||||
# Two commits because a binary log record is written
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
# 20. Read-only statement, a function changes non-trans-table.
|
||||
#
|
||||
select f1() from t1;
|
||||
f1()
|
||||
2
|
||||
2
|
||||
# Two commits because a binary log record is written
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
# 21. Read-write statement: UPDATE, change 0 (transactional) rows.
|
||||
#
|
||||
update t1 set a=2 where a=f1()+10;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
# 22. DDL: ALTER TEMPORARY TABLE, should not cause a 2pc
|
||||
#
|
||||
alter table t2 add column b int default 5;
|
||||
# A commit is done internally by ALTER.
|
||||
call p_verify_status_increment(2, 0, 2, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
# There is nothing left to commit
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
# 23. DDL: RENAME TEMPORARY TABLE, does not start a transaction
|
||||
|
||||
# No test because of Bug#8729 "rename table fails on temporary table"
|
||||
# 24. DDL: TRUNCATE TEMPORARY TABLE, does not start a transaction
|
||||
|
||||
truncate table t2;
|
||||
call p_verify_status_increment(2, 0, 2, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
# There is nothing left to commit
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
# 25. Read-write statement: unqualified DELETE
|
||||
|
||||
delete from t2;
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
# There is nothing left to commit
|
||||
call p_verify_status_increment(2, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
# 25. DDL: DROP TEMPORARY TABLE, does not start a transaction
|
||||
#
|
||||
drop temporary table t2;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
# 26. Verify that SET AUTOCOMMIT issues an implicit commit
|
||||
#
|
||||
insert t1 set a=3;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
set autocommit=1;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
rollback;
|
||||
select a from t1 where a=3;
|
||||
a
|
||||
3
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
delete from t1 where a=3;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
set autocommit=0;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
insert t1 set a=3;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
# Sic: not actually changing the value of autocommit
|
||||
set autocommit=0;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
rollback;
|
||||
select a from t1 where a=3;
|
||||
a
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
# 27. Savepoint management
|
||||
#
|
||||
insert t1 set a=3;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
savepoint a;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
insert t1 set a=4;
|
||||
# Sic: a bug. Binlog did not register itself this time.
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
release savepoint a;
|
||||
rollback;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
select a from t1 where a=3;
|
||||
a
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
# 28. Read-write statement: DO
|
||||
#
|
||||
create table t2 (a int);
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
do (select f1() from t1 where a=2);
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
# 29. Read-write statement: MULTI-DELETE
|
||||
#
|
||||
delete t1, t2 from t1 join t2 on (t1.a=t2.a) where t1.a=2;
|
||||
commit;
|
||||
call p_verify_status_increment(4, 4, 4, 4);
|
||||
SUCCESS
|
||||
|
||||
# 30. Read-write statement: INSERT-SELECT, MULTI-UPDATE, REPLACE-SELECT
|
||||
#
|
||||
insert into t2 select a from t1;
|
||||
commit;
|
||||
replace into t2 select a from t1;
|
||||
commit;
|
||||
call p_verify_status_increment(8, 8, 8, 8);
|
||||
SUCCESS
|
||||
|
||||
update t1, t2 set t1.a=4, t2.a=8 where t1.a=t2.a and t1.a=1;
|
||||
commit;
|
||||
call p_verify_status_increment(4, 4, 4, 4);
|
||||
SUCCESS
|
||||
|
||||
# 31. DDL: various DDL with transactional tables
|
||||
#
|
||||
# Sic: no table is created.
|
||||
create table if not exists t2 (a int) select 6 union select 7;
|
||||
Warnings:
|
||||
Note 1050 Table 't2' already exists
|
||||
# Sic: first commits the statement, and then the transaction.
|
||||
call p_verify_status_increment(4, 4, 4, 4);
|
||||
SUCCESS
|
||||
|
||||
create table t3 select a from t2;
|
||||
call p_verify_status_increment(4, 4, 4, 4);
|
||||
SUCCESS
|
||||
|
||||
alter table t3 add column (b int);
|
||||
call p_verify_status_increment(2, 0, 2, 0);
|
||||
SUCCESS
|
||||
|
||||
alter table t3 rename t4;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
rename table t4 to t3;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
truncate table t3;
|
||||
call p_verify_status_increment(2, 2, 2, 2);
|
||||
SUCCESS
|
||||
|
||||
create view v1 as select * from t2;
|
||||
call p_verify_status_increment(1, 0, 1, 0);
|
||||
SUCCESS
|
||||
|
||||
check table t1;
|
||||
Table Op Msg_type Msg_text
|
||||
test.t1 check status OK
|
||||
call p_verify_status_increment(3, 0, 3, 0);
|
||||
SUCCESS
|
||||
|
||||
# Sic: after this bug is fixed, CHECK leaves no pending transaction
|
||||
commit;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
check table t1, t2, t3;
|
||||
Table Op Msg_type Msg_text
|
||||
test.t1 check status OK
|
||||
test.t2 check status OK
|
||||
test.t3 check status OK
|
||||
call p_verify_status_increment(6, 0, 6, 0);
|
||||
SUCCESS
|
||||
|
||||
commit;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
drop view v1;
|
||||
call p_verify_status_increment(0, 0, 0, 0);
|
||||
SUCCESS
|
||||
|
||||
#
|
||||
# Cleanup
|
||||
#
|
||||
drop table t1;
|
||||
drop procedure p_verify_status_increment;
|
||||
drop function f1;
|
@ -373,7 +373,7 @@ master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
|
||||
master-bin.000001 # Query # # use `test`; BEGIN
|
||||
master-bin.000001 # Table_map # # table_id: # (test.t2)
|
||||
master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
|
||||
master-bin.000001 # Xid # # COMMIT /* XID */
|
||||
master-bin.000001 # Query # # use `test`; COMMIT
|
||||
master-bin.000001 # Query # # use `test`; DROP TABLE t2
|
||||
master-bin.000001 # Table_map # # table_id: # (test.t1)
|
||||
master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
|
||||
@ -384,7 +384,6 @@ master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
|
||||
master-bin.000001 # Table_map # # table_id: # (test.t1)
|
||||
master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
|
||||
master-bin.000001 # Query # # use `test`; TRUNCATE table t2
|
||||
master-bin.000001 # Xid # # COMMIT /* XID */
|
||||
master-bin.000001 # Table_map # # table_id: # (test.t1)
|
||||
master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
|
||||
master-bin.000001 # Query # # use `test`; DROP TABLE `t1` /* generated by server */
|
||||
|
@ -244,7 +244,6 @@ master-bin.000001 # Query # # use `test`; insert into t2 values (20)
|
||||
master-bin.000001 # Query # # use `test`; drop table t1,t2
|
||||
master-bin.000001 # Query # # use `test`; create temporary table ti (a int) engine=innodb
|
||||
master-bin.000001 # Query # # use `test`; insert into ti values(1)
|
||||
master-bin.000001 # Xid # # COMMIT /* XID */
|
||||
master-bin.000001 # Query # # use `test`; create temporary table t1 (a int) engine=myisam
|
||||
master-bin.000001 # Query # # use `test`; insert t1 values (1)
|
||||
master-bin.000001 # Query # # use `test`; create table t0 (n int)
|
||||
@ -349,11 +348,10 @@ master-bin.000001 # Query # # use `test`; INSERT INTO t1 values (7,7)
|
||||
master-bin.000001 # Query # # use `test`; INSERT INTO t1 values (8,8)
|
||||
master-bin.000001 # Query # # use `test`; INSERT INTO t1 values (9,9)
|
||||
master-bin.000001 # Query # # use `test`; TRUNCATE table t2
|
||||
master-bin.000001 # Xid # # COMMIT /* XID */
|
||||
master-bin.000001 # Query # # use `test`; INSERT INTO t1 values (10,10)
|
||||
master-bin.000001 # Query # # use `test`; BEGIN
|
||||
master-bin.000001 # Query # # use `test`; INSERT INTO t2 values (100,100)
|
||||
master-bin.000001 # Xid # # COMMIT /* XID */
|
||||
master-bin.000001 # Query # # use `test`; COMMIT
|
||||
master-bin.000001 # Query # # use `test`; DROP TABLE t1,t2
|
||||
reset master;
|
||||
create table t1 (a int) engine=innodb;
|
||||
|
@ -21,3 +21,5 @@ rpl_ndb_mix_innodb : Bug #32720 Test rpl_ndb_mix_innodb fails on SPARC a
|
||||
# the below testcase have been reworked to avoid the bug, test contains comment, keep bug open
|
||||
|
||||
#rpl_ndb_dd_advance : Bug#25913 rpl_ndb_dd_advance fails randomly
|
||||
rpl_ndb_circular : Bug#33849 COMMIT event missing in cluster circular replication.
|
||||
rpl_ndb_circular_simplex : Bug#33849 COMMIT event missing in cluster circular replication.
|
||||
|
6
mysql-test/t/commit_1innodb.test
Normal file
6
mysql-test/t/commit_1innodb.test
Normal file
@ -0,0 +1,6 @@
|
||||
-- source include/have_log_bin.inc
|
||||
-- source include/have_innodb.inc
|
||||
|
||||
let $engine_type = InnoDB;
|
||||
|
||||
-- source include/commit.inc
|
@ -283,6 +283,7 @@ static void run_query(THD *thd, char *buf, char *end,
|
||||
thd_ndb->m_error_code,
|
||||
(int) thd->is_error(), thd->is_slave_error);
|
||||
}
|
||||
close_thread_tables(thd);
|
||||
/*
|
||||
XXX: this code is broken. mysql_parse()/mysql_reset_thd_for_next_command()
|
||||
can not be called from within a statement, and
|
||||
|
615
sql/handler.cc
615
sql/handler.cc
@ -575,6 +575,295 @@ void ha_close_connection(THD* thd)
|
||||
/* ========================================================================
|
||||
======================= TRANSACTIONS ===================================*/
|
||||
|
||||
/**
|
||||
Transaction handling in the server
|
||||
==================================
|
||||
|
||||
In each client connection, MySQL maintains two transactional
|
||||
states:
|
||||
- a statement transaction,
|
||||
- a standard, also called normal transaction.
|
||||
|
||||
Historical note
|
||||
---------------
|
||||
"Statement transaction" is a non-standard term that comes
|
||||
from the times when MySQL supported BerkeleyDB storage engine.
|
||||
|
||||
First of all, it should be said that in BerkeleyDB auto-commit
|
||||
mode auto-commits operations that are atomic to the storage
|
||||
engine itself, such as a write of a record, and are too
|
||||
high-granular to be atomic from the application perspective
|
||||
(MySQL). One SQL statement could involve many BerkeleyDB
|
||||
auto-committed operations and thus BerkeleyDB auto-commit was of
|
||||
little use to MySQL.
|
||||
|
||||
Secondly, instead of SQL standard savepoints, BerkeleyDB
|
||||
provided the concept of "nested transactions". In a nutshell,
|
||||
transactions could be arbitrarily nested, but when the parent
|
||||
transaction was committed or aborted, all its child (nested)
|
||||
transactions were handled committed or aborted as well.
|
||||
Commit of a nested transaction, in turn, made its changes
|
||||
visible, but not durable: it destroyed the nested transaction,
|
||||
all its changes would become available to the parent and
|
||||
currently active nested transactions of this parent.
|
||||
|
||||
So the mechanism of nested transactions was employed to
|
||||
provide "all or nothing" guarantee of SQL statements
|
||||
required by the standard.
|
||||
A nested transaction would be created at start of each SQL
|
||||
statement, and destroyed (committed or aborted) at statement
|
||||
end. Such nested transaction was internally referred to as
|
||||
a "statement transaction" and gave birth to the term.
|
||||
|
||||
<Historical note ends>
|
||||
|
||||
Since then a statement transaction is started for each statement
|
||||
that accesses transactional tables or uses the binary log. If
|
||||
the statement succeeds, the statement transaction is committed.
|
||||
If the statement fails, the transaction is rolled back. Commits
|
||||
of statement transactions are not durable -- each such
|
||||
transaction is nested in the normal transaction, and if the
|
||||
normal transaction is rolled back, the effects of all enclosed
|
||||
statement transactions are undone as well. Technically,
|
||||
a statement transaction can be viewed as a savepoint which is
|
||||
maintained automatically in order to make effects of one
|
||||
statement atomic.
|
||||
|
||||
The normal transaction is started by the user and is ended
|
||||
usually upon a user request as well. The normal transaction
|
||||
encloses transactions of all statements issued between
|
||||
its beginning and its end.
|
||||
In autocommit mode, the normal transaction is equivalent
|
||||
to the statement transaction.
|
||||
|
||||
Since MySQL supports PSEA (pluggable storage engine
|
||||
architecture), more than one transactional engine can be
|
||||
active at a time. Hence transactions, from the server
|
||||
point of view, are always distributed. In particular,
|
||||
transactional state is maintained independently for each
|
||||
engine. In order to commit a transaction the two phase
|
||||
commit protocol is employed.
|
||||
|
||||
Not all statements are executed in context of a transaction.
|
||||
Administrative and status information statements do not modify
|
||||
engine data, and thus do not start a statement transaction and
|
||||
also have no effect on the normal transaction. Examples of such
|
||||
statements are SHOW STATUS and RESET SLAVE.
|
||||
|
||||
Similarly DDL statements are not transactional,
|
||||
and therefore a transaction is [almost] never started for a DDL
|
||||
statement. The difference between a DDL statement and a purely
|
||||
administrative statement though is that a DDL statement always
|
||||
commits the current transaction before proceeding, if there is
|
||||
any.
|
||||
|
||||
At last, SQL statements that work with non-transactional
|
||||
engines also have no effect on the transaction state of the
|
||||
connection. Even though they are written to the binary log,
|
||||
and the binary log is, overall, transactional, the writes
|
||||
are done in "write-through" mode, directly to the binlog
|
||||
file, followed with a OS cache sync, in other words,
|
||||
bypassing the binlog undo log (translog).
|
||||
They do not commit the current normal transaction.
|
||||
A failure of a statement that uses non-transactional tables
|
||||
would cause a rollback of the statement transaction, but
|
||||
in case there no non-transactional tables are used,
|
||||
no statement transaction is started.
|
||||
|
||||
Data layout
|
||||
-----------
|
||||
|
||||
The server stores its transaction-related data in
|
||||
thd->transaction. This structure has two members of type
|
||||
THD_TRANS. These members correspond to the statement and
|
||||
normal transactions respectively:
|
||||
|
||||
- thd->transaction.stmt contains a list of engines
|
||||
that are participating in the given statement
|
||||
- thd->transaction.all contains a list of engines that
|
||||
have participated in any of the statement transactions started
|
||||
within the context of the normal transaction.
|
||||
Each element of the list contains a pointer to the storage
|
||||
engine, engine-specific transactional data, and engine-specific
|
||||
transaction flags.
|
||||
|
||||
In autocommit mode thd->transaction.all is empty.
|
||||
Instead, data of thd->transaction.stmt is
|
||||
used to commit/rollback the normal transaction.
|
||||
|
||||
The list of registered engines has a few important properties:
|
||||
- no engine is registered in the list twice
|
||||
- engines are present in the list a reverse temporal order --
|
||||
new participants are always added to the beginning of the list.
|
||||
|
||||
Transaction life cycle
|
||||
----------------------
|
||||
|
||||
When a new connection is established, thd->transaction
|
||||
members are initialized to an empty state.
|
||||
If a statement uses any tables, all affected engines
|
||||
are registered in the statement engine list. In
|
||||
non-autocommit mode, the same engines are registered in
|
||||
the normal transaction list.
|
||||
At the end of the statement, the server issues a commit
|
||||
or a roll back for all engines in the statement list.
|
||||
At this point transaction flags of an engine, if any, are
|
||||
propagated from the statement list to the list of the normal
|
||||
transaction.
|
||||
When commit/rollback is finished, the statement list is
|
||||
cleared. It will be filled in again by the next statement,
|
||||
and emptied again at the next statement's end.
|
||||
|
||||
The normal transaction is committed in a similar way
|
||||
(by going over all engines in thd->transaction.all list)
|
||||
but at different times:
|
||||
- upon COMMIT SQL statement is issued by the user
|
||||
- implicitly, by the server, at the beginning of a DDL statement
|
||||
or SET AUTOCOMMIT={0|1} statement.
|
||||
|
||||
The normal transaction can be rolled back as well:
|
||||
- if the user has requested so, by issuing ROLLBACK SQL
|
||||
statement
|
||||
- if one of the storage engines requested a rollback
|
||||
by setting thd->transaction_rollback_request. This may
|
||||
happen in case, e.g., when the transaction in the engine was
|
||||
chosen a victim of the internal deadlock resolution algorithm
|
||||
and rolled back internally. When such a situation happens, there
|
||||
is little the server can do and the only option is to rollback
|
||||
transactions in all other participating engines. In this case
|
||||
the rollback is accompanied by an error sent to the user.
|
||||
|
||||
As follows from the use cases above, the normal transaction
|
||||
is never committed when there is an outstanding statement
|
||||
transaction. In most cases there is no conflict, since
|
||||
commits of the normal transaction are issued by a stand-alone
|
||||
administrative or DDL statement, thus no outstanding statement
|
||||
transaction of the previous statement exists. Besides,
|
||||
all statements that manipulate with the normal transaction
|
||||
are prohibited in stored functions and triggers, therefore
|
||||
no conflicting situation can occur in a sub-statement either.
|
||||
The remaining rare cases when the server explicitly has
|
||||
to commit the statement transaction prior to committing the normal
|
||||
one cover error-handling scenarios (see for example
|
||||
SQLCOM_LOCK_TABLES).
|
||||
|
||||
When committing a statement or a normal transaction, the server
|
||||
either uses the two-phase commit protocol, or issues a commit
|
||||
in each engine independently. The two-phase commit protocol
|
||||
is used only if:
|
||||
- all participating engines support two-phase commit (provide
|
||||
handlerton::prepare PSEA API call) and
|
||||
- transactions in at least two engines modify data (i.e. are
|
||||
not read-only).
|
||||
|
||||
Note that the two phase commit is used for
|
||||
statement transactions, even though they are not durable anyway.
|
||||
This is done to ensure logical consistency of data in a multiple-
|
||||
engine transaction.
|
||||
For example, imagine that some day MySQL supports unique
|
||||
constraint checks deferred till the end of statement. In such
|
||||
case a commit in one of the engines may yield ER_DUP_KEY,
|
||||
and MySQL should be able to gracefully abort statement
|
||||
transactions of other participants.
|
||||
|
||||
After the normal transaction has been committed,
|
||||
thd->transaction.all list is cleared.
|
||||
|
||||
When a connection is closed, the current normal transaction, if
|
||||
any, is rolled back.
|
||||
|
||||
Roles and responsibilities
|
||||
--------------------------
|
||||
|
||||
The server has no way to know that an engine participates in
|
||||
the statement and a transaction has been started
|
||||
in it unless the engine says so. Thus, in order to be
|
||||
a part of a transaction, the engine must "register" itself.
|
||||
This is done by invoking trans_register_ha() server call.
|
||||
Normally the engine registers itself whenever handler::external_lock()
|
||||
is called. trans_register_ha() can be invoked many times: if
|
||||
an engine is already registered, the call does nothing.
|
||||
In case autocommit is not set, the engine must register itself
|
||||
twice -- both in the statement list and in the normal transaction
|
||||
list.
|
||||
In which list to register is a parameter of trans_register_ha().
|
||||
|
||||
Note, that although the registration interface in itself is
|
||||
fairly clear, the current usage practice often leads to undesired
|
||||
effects. E.g. since a call to trans_register_ha() in most engines
|
||||
is embedded into implementation of handler::external_lock(), some
|
||||
DDL statements start a transaction (at least from the server
|
||||
point of view) even though they are not expected to. E.g.
|
||||
CREATE TABLE does not start a transaction, since
|
||||
handler::external_lock() is never called during CREATE TABLE. But
|
||||
CREATE TABLE ... SELECT does, since handler::external_lock() is
|
||||
called for the table that is being selected from. This has no
|
||||
practical effects currently, but must be kept in mind
|
||||
nevertheless.
|
||||
|
||||
Once an engine is registered, the server will do the rest
|
||||
of the work.
|
||||
|
||||
During statement execution, whenever any of data-modifying
|
||||
PSEA API methods is used, e.g. handler::write_row() or
|
||||
handler::update_row(), the read-write flag is raised in the
|
||||
statement transaction for the involved engine.
|
||||
Currently All PSEA calls are "traced", and the data can not be
|
||||
changed in a way other than issuing a PSEA call. Important:
|
||||
unless this invariant is preserved the server will not know that
|
||||
a transaction in a given engine is read-write and will not
|
||||
involve the two-phase commit protocol!
|
||||
|
||||
At the end of a statement, server call
|
||||
ha_autocommit_or_rollback() is invoked. This call in turn
|
||||
invokes handlerton::prepare() for every involved engine.
|
||||
Prepare is followed by a call to handlerton::commit_one_phase()
|
||||
If a one-phase commit will suffice, handlerton::prepare() is not
|
||||
invoked and the server only calls handlerton::commit_one_phase().
|
||||
At statement commit, the statement-related read-write engine
|
||||
flag is propagated to the corresponding flag in the normal
|
||||
transaction. When the commit is complete, the list of registered
|
||||
engines is cleared.
|
||||
|
||||
Rollback is handled in a similar fashion.
|
||||
|
||||
Additional notes on DDL and the normal transaction.
|
||||
---------------------------------------------------
|
||||
|
||||
DDLs and operations with non-transactional engines
|
||||
do not "register" in thd->transaction lists, and thus do not
|
||||
modify the transaction state. Besides, each DDL in
|
||||
MySQL is prefixed with an implicit normal transaction commit
|
||||
(a call to end_active_trans()), and thus leaves nothing
|
||||
to modify.
|
||||
However, as it has been pointed out with CREATE TABLE .. SELECT,
|
||||
some DDL statements can start a *new* transaction.
|
||||
|
||||
Behaviour of the server in this case is currently badly
|
||||
defined.
|
||||
DDL statements use a form of "semantic" logging
|
||||
to maintain atomicity: if CREATE TABLE .. SELECT failed,
|
||||
the newly created table is deleted.
|
||||
In addition, some DDL statements issue interim transaction
|
||||
commits: e.g. ALTER TABLE issues a commit after data is copied
|
||||
from the original table to the internal temporary table. Other
|
||||
statements, e.g. CREATE TABLE ... SELECT do not always commit
|
||||
after itself.
|
||||
And finally there is a group of DDL statements such as
|
||||
RENAME/DROP TABLE that doesn't start a new transaction
|
||||
and doesn't commit.
|
||||
|
||||
This diversity makes it hard to say what will happen if
|
||||
by chance a stored function is invoked during a DDL --
|
||||
whether any modifications it makes will be committed or not
|
||||
is not clear. Fortunately, SQL grammar of few DDLs allows
|
||||
invocation of a stored function.
|
||||
|
||||
A consistent behaviour is perhaps to always commit the normal
|
||||
transaction after all DDLs, just like the statement transaction
|
||||
is always committed at the end of all statements.
|
||||
*/
|
||||
|
||||
/**
|
||||
Register a storage engine for a transaction.
|
||||
|
||||
@ -592,7 +881,7 @@ void ha_close_connection(THD* thd)
|
||||
void trans_register_ha(THD *thd, bool all, handlerton *ht_arg)
|
||||
{
|
||||
THD_TRANS *trans;
|
||||
handlerton **ht;
|
||||
Ha_trx_info *ha_info;
|
||||
DBUG_ENTER("trans_register_ha");
|
||||
DBUG_PRINT("enter",("%s", all ? "all" : "stmt"));
|
||||
|
||||
@ -604,12 +893,13 @@ void trans_register_ha(THD *thd, bool all, handlerton *ht_arg)
|
||||
else
|
||||
trans= &thd->transaction.stmt;
|
||||
|
||||
for (ht=trans->ht; *ht; ht++)
|
||||
if (*ht == ht_arg)
|
||||
DBUG_VOID_RETURN; /* already registered, return */
|
||||
ha_info= thd->ha_data[ht_arg->slot].ha_info + static_cast<unsigned>(all);
|
||||
|
||||
if (ha_info->is_started())
|
||||
DBUG_VOID_RETURN; /* already registered, return */
|
||||
|
||||
ha_info->register_ha(trans, ht_arg);
|
||||
|
||||
trans->ht[trans->nht++]=ht_arg;
|
||||
DBUG_ASSERT(*ht == ht_arg);
|
||||
trans->no_2pc|=(ht_arg->prepare==0);
|
||||
if (thd->transaction.xid_state.xid.is_null())
|
||||
thd->transaction.xid_state.xid.set(thd->query_id);
|
||||
@ -626,18 +916,19 @@ int ha_prepare(THD *thd)
|
||||
{
|
||||
int error=0, all=1;
|
||||
THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
|
||||
handlerton **ht=trans->ht;
|
||||
Ha_trx_info *ha_info= trans->ha_list;
|
||||
DBUG_ENTER("ha_prepare");
|
||||
#ifdef USING_TRANSACTIONS
|
||||
if (trans->nht)
|
||||
if (ha_info)
|
||||
{
|
||||
for (; *ht; ht++)
|
||||
for (; ha_info; ha_info= ha_info->next())
|
||||
{
|
||||
int err;
|
||||
handlerton *ht= ha_info->ht();
|
||||
status_var_increment(thd->status_var.ha_prepare_count);
|
||||
if ((*ht)->prepare)
|
||||
if (ht->prepare)
|
||||
{
|
||||
if ((err= (*(*ht)->prepare)(*ht, thd, all)))
|
||||
if ((err= ht->prepare(ht, thd, all)))
|
||||
{
|
||||
my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
|
||||
ha_rollback_trans(thd, all);
|
||||
@ -649,7 +940,7 @@ int ha_prepare(THD *thd)
|
||||
{
|
||||
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
|
||||
ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA),
|
||||
ha_resolve_storage_engine_name(*ht));
|
||||
ha_resolve_storage_engine_name(ht));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -657,6 +948,62 @@ int ha_prepare(THD *thd)
|
||||
DBUG_RETURN(error);
|
||||
}
|
||||
|
||||
/**
|
||||
Check if we can skip the two-phase commit.
|
||||
|
||||
A helper function to evaluate if two-phase commit is mandatory.
|
||||
As a side effect, propagates the read-only/read-write flags
|
||||
of the statement transaction to its enclosing normal transaction.
|
||||
|
||||
@retval TRUE we must run a two-phase commit. Returned
|
||||
if we have at least two engines with read-write changes.
|
||||
@retval FALSE Don't need two-phase commit. Even if we have two
|
||||
transactional engines, we can run two independent
|
||||
commits if changes in one of the engines are read-only.
|
||||
*/
|
||||
|
||||
static
|
||||
bool
|
||||
ha_check_and_coalesce_trx_read_only(THD *thd, Ha_trx_info *ha_list,
|
||||
bool all)
|
||||
{
|
||||
/* The number of storage engines that have actual changes. */
|
||||
unsigned rw_ha_count= 0;
|
||||
Ha_trx_info *ha_info;
|
||||
|
||||
for (ha_info= ha_list; ha_info; ha_info= ha_info->next())
|
||||
{
|
||||
if (ha_info->is_trx_read_write())
|
||||
++rw_ha_count;
|
||||
|
||||
if (! all)
|
||||
{
|
||||
Ha_trx_info *ha_info_all= &thd->ha_data[ha_info->ht()->slot].ha_info[1];
|
||||
DBUG_ASSERT(ha_info != ha_info_all);
|
||||
/*
|
||||
Merge read-only/read-write information about statement
|
||||
transaction to its enclosing normal transaction. Do this
|
||||
only if in a real transaction -- that is, if we know
|
||||
that ha_info_all is registered in thd->transaction.all.
|
||||
Since otherwise we only clutter the normal transaction flags.
|
||||
*/
|
||||
if (ha_info_all->is_started()) /* FALSE if autocommit. */
|
||||
ha_info_all->coalesce_trx_with(ha_info);
|
||||
}
|
||||
else if (rw_ha_count > 1)
|
||||
{
|
||||
/*
|
||||
It is a normal transaction, so we don't need to merge read/write
|
||||
information up, and the need for two-phase commit has been
|
||||
already established. Break the loop prematurely.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rw_ha_count > 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@retval
|
||||
0 ok
|
||||
@ -674,12 +1021,25 @@ int ha_prepare(THD *thd)
|
||||
int ha_commit_trans(THD *thd, bool all)
|
||||
{
|
||||
int error= 0, cookie= 0;
|
||||
/*
|
||||
'all' means that this is either an explicit commit issued by
|
||||
user, or an implicit commit issued by a DDL.
|
||||
*/
|
||||
THD_TRANS *trans= all ? &thd->transaction.all : &thd->transaction.stmt;
|
||||
bool is_real_trans= all || thd->transaction.all.nht == 0;
|
||||
handlerton **ht= trans->ht;
|
||||
bool is_real_trans= all || thd->transaction.all.ha_list == 0;
|
||||
Ha_trx_info *ha_info= trans->ha_list;
|
||||
my_xid xid= thd->transaction.xid_state.xid.get_my_xid();
|
||||
DBUG_ENTER("ha_commit_trans");
|
||||
|
||||
/*
|
||||
We must not commit the normal transaction if a statement
|
||||
transaction is pending. Otherwise statement transaction
|
||||
flags will not get propagated to its normal transaction's
|
||||
counterpart.
|
||||
*/
|
||||
DBUG_ASSERT(thd->transaction.stmt.ha_list == NULL ||
|
||||
trans == &thd->transaction.stmt);
|
||||
|
||||
if (thd->in_sub_stmt)
|
||||
{
|
||||
/*
|
||||
@ -701,8 +1061,10 @@ int ha_commit_trans(THD *thd, bool all)
|
||||
DBUG_RETURN(2);
|
||||
}
|
||||
#ifdef USING_TRANSACTIONS
|
||||
if (trans->nht)
|
||||
if (ha_info)
|
||||
{
|
||||
bool must_2pc;
|
||||
|
||||
if (is_real_trans && wait_if_global_read_lock(thd, 0, 0))
|
||||
{
|
||||
ha_rollback_trans(thd, all);
|
||||
@ -727,12 +1089,26 @@ int ha_commit_trans(THD *thd, bool all)
|
||||
if (is_real_trans) /* not a statement commit */
|
||||
thd->stmt_map.close_transient_cursors();
|
||||
|
||||
if (!trans->no_2pc && trans->nht > 1)
|
||||
must_2pc= ha_check_and_coalesce_trx_read_only(thd, ha_info, all);
|
||||
|
||||
if (!trans->no_2pc && must_2pc)
|
||||
{
|
||||
for (; *ht && !error; ht++)
|
||||
for (; ha_info && !error; ha_info= ha_info->next())
|
||||
{
|
||||
int err;
|
||||
if ((err= (*(*ht)->prepare)(*ht, thd, all)))
|
||||
handlerton *ht= ha_info->ht();
|
||||
/*
|
||||
Do not call two-phase commit if this particular
|
||||
transaction is read-only. This allows for simpler
|
||||
implementation in engines that are always read-only.
|
||||
*/
|
||||
if (! ha_info->is_trx_read_write())
|
||||
continue;
|
||||
/*
|
||||
Sic: we know that prepare() is not NULL since otherwise
|
||||
trans->no_2pc would have been set.
|
||||
*/
|
||||
if ((err= ht->prepare(ht, thd, all)))
|
||||
{
|
||||
my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
|
||||
error= 1;
|
||||
@ -770,24 +1146,26 @@ int ha_commit_one_phase(THD *thd, bool all)
|
||||
{
|
||||
int error=0;
|
||||
THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
|
||||
bool is_real_trans=all || thd->transaction.all.nht == 0;
|
||||
handlerton **ht=trans->ht;
|
||||
bool is_real_trans=all || thd->transaction.all.ha_list == 0;
|
||||
Ha_trx_info *ha_info= trans->ha_list, *ha_info_next;
|
||||
DBUG_ENTER("ha_commit_one_phase");
|
||||
#ifdef USING_TRANSACTIONS
|
||||
if (trans->nht)
|
||||
if (ha_info)
|
||||
{
|
||||
for (ht=trans->ht; *ht; ht++)
|
||||
for (; ha_info; ha_info= ha_info_next)
|
||||
{
|
||||
int err;
|
||||
if ((err= (*(*ht)->commit)(*ht, thd, all)))
|
||||
handlerton *ht= ha_info->ht();
|
||||
if ((err= ht->commit(ht, thd, all)))
|
||||
{
|
||||
my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
|
||||
error=1;
|
||||
}
|
||||
status_var_increment(thd->status_var.ha_commit_count);
|
||||
*ht= 0;
|
||||
ha_info_next= ha_info->next();
|
||||
ha_info->reset(); /* keep it conveniently zero-filled */
|
||||
}
|
||||
trans->nht=0;
|
||||
trans->ha_list= 0;
|
||||
trans->no_2pc=0;
|
||||
if (is_real_trans)
|
||||
thd->transaction.xid_state.xid.null();
|
||||
@ -810,8 +1188,17 @@ int ha_rollback_trans(THD *thd, bool all)
|
||||
{
|
||||
int error=0;
|
||||
THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
|
||||
bool is_real_trans=all || thd->transaction.all.nht == 0;
|
||||
Ha_trx_info *ha_info= trans->ha_list, *ha_info_next;
|
||||
bool is_real_trans=all || thd->transaction.all.ha_list == 0;
|
||||
DBUG_ENTER("ha_rollback_trans");
|
||||
|
||||
/*
|
||||
We must not rollback the normal transaction if a statement
|
||||
transaction is pending.
|
||||
*/
|
||||
DBUG_ASSERT(thd->transaction.stmt.ha_list == NULL ||
|
||||
trans == &thd->transaction.stmt);
|
||||
|
||||
if (thd->in_sub_stmt)
|
||||
{
|
||||
/*
|
||||
@ -826,24 +1213,26 @@ int ha_rollback_trans(THD *thd, bool all)
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
#ifdef USING_TRANSACTIONS
|
||||
if (trans->nht)
|
||||
if (ha_info)
|
||||
{
|
||||
/* Close all cursors that can not survive ROLLBACK */
|
||||
if (is_real_trans) /* not a statement commit */
|
||||
thd->stmt_map.close_transient_cursors();
|
||||
|
||||
for (handlerton **ht=trans->ht; *ht; ht++)
|
||||
for (; ha_info; ha_info= ha_info_next)
|
||||
{
|
||||
int err;
|
||||
if ((err= (*(*ht)->rollback)(*ht, thd, all)))
|
||||
handlerton *ht= ha_info->ht();
|
||||
if ((err= ht->rollback(ht, thd, all)))
|
||||
{ // cannot happen
|
||||
my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
|
||||
error=1;
|
||||
}
|
||||
status_var_increment(thd->status_var.ha_rollback_count);
|
||||
*ht= 0;
|
||||
ha_info_next= ha_info->next();
|
||||
ha_info->reset(); /* keep it conveniently zero-filled */
|
||||
}
|
||||
trans->nht=0;
|
||||
trans->ha_list= 0;
|
||||
trans->no_2pc=0;
|
||||
if (is_real_trans)
|
||||
thd->transaction.xid_state.xid.null();
|
||||
@ -889,17 +1278,19 @@ int ha_autocommit_or_rollback(THD *thd, int error)
|
||||
{
|
||||
DBUG_ENTER("ha_autocommit_or_rollback");
|
||||
#ifdef USING_TRANSACTIONS
|
||||
if (thd->transaction.stmt.nht)
|
||||
if (thd->transaction.stmt.ha_list)
|
||||
{
|
||||
if (!error)
|
||||
{
|
||||
if (ha_commit_stmt(thd))
|
||||
if (ha_commit_trans(thd, 0))
|
||||
error=1;
|
||||
}
|
||||
else if (thd->transaction_rollback_request && !thd->in_sub_stmt)
|
||||
(void) ha_rollback(thd);
|
||||
else
|
||||
(void) ha_rollback_stmt(thd);
|
||||
else
|
||||
{
|
||||
(void) ha_rollback_trans(thd, 0);
|
||||
if (thd->transaction_rollback_request && !thd->in_sub_stmt)
|
||||
(void) ha_rollback(thd);
|
||||
}
|
||||
|
||||
thd->variables.tx_isolation=thd->session_tx_isolation;
|
||||
}
|
||||
@ -1246,43 +1637,49 @@ int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv)
|
||||
int error=0;
|
||||
THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
|
||||
&thd->transaction.all);
|
||||
handlerton **ht=trans->ht, **end_ht;
|
||||
Ha_trx_info *ha_info, *ha_info_next;
|
||||
|
||||
DBUG_ENTER("ha_rollback_to_savepoint");
|
||||
|
||||
trans->nht=sv->nht;
|
||||
trans->no_2pc=0;
|
||||
end_ht=ht+sv->nht;
|
||||
/*
|
||||
rolling back to savepoint in all storage engines that were part of the
|
||||
transaction when the savepoint was set
|
||||
*/
|
||||
for (; ht < end_ht; ht++)
|
||||
for (ha_info= sv->ha_list; ha_info; ha_info= ha_info->next())
|
||||
{
|
||||
int err;
|
||||
DBUG_ASSERT((*ht)->savepoint_set != 0);
|
||||
if ((err= (*(*ht)->savepoint_rollback)(*ht, thd, (uchar *)(sv+1)+(*ht)->savepoint_offset)))
|
||||
handlerton *ht= ha_info->ht();
|
||||
DBUG_ASSERT(ht);
|
||||
DBUG_ASSERT(ht->savepoint_set != 0);
|
||||
if ((err= ht->savepoint_rollback(ht, thd,
|
||||
(uchar *)(sv+1)+ht->savepoint_offset)))
|
||||
{ // cannot happen
|
||||
my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
|
||||
error=1;
|
||||
}
|
||||
status_var_increment(thd->status_var.ha_savepoint_rollback_count);
|
||||
trans->no_2pc|=(*ht)->prepare == 0;
|
||||
trans->no_2pc|= ht->prepare == 0;
|
||||
}
|
||||
/*
|
||||
rolling back the transaction in all storage engines that were not part of
|
||||
the transaction when the savepoint was set
|
||||
*/
|
||||
for (; *ht ; ht++)
|
||||
for (ha_info= trans->ha_list; ha_info != sv->ha_list;
|
||||
ha_info= ha_info_next)
|
||||
{
|
||||
int err;
|
||||
if ((err= (*(*ht)->rollback)(*ht, thd, !thd->in_sub_stmt)))
|
||||
handlerton *ht= ha_info->ht();
|
||||
if ((err= ht->rollback(ht, thd, !thd->in_sub_stmt)))
|
||||
{ // cannot happen
|
||||
my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
|
||||
error=1;
|
||||
}
|
||||
status_var_increment(thd->status_var.ha_rollback_count);
|
||||
*ht=0; // keep it conveniently zero-filled
|
||||
ha_info_next= ha_info->next();
|
||||
ha_info->reset(); /* keep it conveniently zero-filled */
|
||||
}
|
||||
trans->ha_list= sv->ha_list;
|
||||
DBUG_RETURN(error);
|
||||
}
|
||||
|
||||
@ -1297,26 +1694,32 @@ int ha_savepoint(THD *thd, SAVEPOINT *sv)
|
||||
int error=0;
|
||||
THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
|
||||
&thd->transaction.all);
|
||||
handlerton **ht=trans->ht;
|
||||
Ha_trx_info *ha_info= trans->ha_list;
|
||||
DBUG_ENTER("ha_savepoint");
|
||||
#ifdef USING_TRANSACTIONS
|
||||
for (; *ht; ht++)
|
||||
for (; ha_info; ha_info= ha_info->next())
|
||||
{
|
||||
int err;
|
||||
if (! (*ht)->savepoint_set)
|
||||
handlerton *ht= ha_info->ht();
|
||||
DBUG_ASSERT(ht);
|
||||
if (! ht->savepoint_set)
|
||||
{
|
||||
my_error(ER_CHECK_NOT_IMPLEMENTED, MYF(0), "SAVEPOINT");
|
||||
error=1;
|
||||
break;
|
||||
}
|
||||
if ((err= (*(*ht)->savepoint_set)(*ht, thd, (uchar *)(sv+1)+(*ht)->savepoint_offset)))
|
||||
if ((err= ht->savepoint_set(ht, thd, (uchar *)(sv+1)+ht->savepoint_offset)))
|
||||
{ // cannot happen
|
||||
my_error(ER_GET_ERRNO, MYF(0), err);
|
||||
error=1;
|
||||
}
|
||||
status_var_increment(thd->status_var.ha_savepoint_count);
|
||||
}
|
||||
sv->nht=trans->nht;
|
||||
/*
|
||||
Remember the list of registered storage engines. All new
|
||||
engines are prepended to the beginning of the list.
|
||||
*/
|
||||
sv->ha_list= trans->ha_list;
|
||||
#endif /* USING_TRANSACTIONS */
|
||||
DBUG_RETURN(error);
|
||||
}
|
||||
@ -1324,20 +1727,19 @@ int ha_savepoint(THD *thd, SAVEPOINT *sv)
|
||||
int ha_release_savepoint(THD *thd, SAVEPOINT *sv)
|
||||
{
|
||||
int error=0;
|
||||
THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
|
||||
&thd->transaction.all);
|
||||
handlerton **ht=trans->ht, **end_ht;
|
||||
Ha_trx_info *ha_info= sv->ha_list;
|
||||
DBUG_ENTER("ha_release_savepoint");
|
||||
|
||||
end_ht=ht+sv->nht;
|
||||
for (; ht < end_ht; ht++)
|
||||
for (; ha_info; ha_info= ha_info->next())
|
||||
{
|
||||
int err;
|
||||
if (!(*ht)->savepoint_release)
|
||||
handlerton *ht= ha_info->ht();
|
||||
/* Savepoint life time is enclosed into transaction life time. */
|
||||
DBUG_ASSERT(ht);
|
||||
if (!ht->savepoint_release)
|
||||
continue;
|
||||
if ((err= (*(*ht)->savepoint_release)(*ht, thd,
|
||||
(uchar *)(sv+1)+
|
||||
(*ht)->savepoint_offset)))
|
||||
if ((err= ht->savepoint_release(ht, thd,
|
||||
(uchar *)(sv+1) + ht->savepoint_offset)))
|
||||
{ // cannot happen
|
||||
my_error(ER_GET_ERRNO, MYF(0), err);
|
||||
error=1;
|
||||
@ -2506,6 +2908,36 @@ int handler::ha_check(THD *thd, HA_CHECK_OPT *check_opt)
|
||||
return update_frm_version(table);
|
||||
}
|
||||
|
||||
/**
|
||||
A helper function to mark a transaction read-write,
|
||||
if it is started.
|
||||
*/
|
||||
|
||||
inline
|
||||
void
|
||||
handler::mark_trx_read_write()
|
||||
{
|
||||
Ha_trx_info *ha_info= &ha_thd()->ha_data[ht->slot].ha_info[0];
|
||||
/*
|
||||
When a storage engine method is called, the transaction must
|
||||
have been started, unless it's a DDL call, for which the
|
||||
storage engine starts the transaction internally, and commits
|
||||
it internally, without registering in the ha_list.
|
||||
Unfortunately here we can't know know for sure if the engine
|
||||
has registered the transaction or not, so we must check.
|
||||
*/
|
||||
if (ha_info->is_started())
|
||||
{
|
||||
DBUG_ASSERT(has_transactions());
|
||||
/*
|
||||
table_share can be NULL in ha_delete_table(). See implementation
|
||||
of standalone function ha_delete_table() in sql_base.cc.
|
||||
*/
|
||||
if (table_share == NULL || table_share->tmp_table == NO_TMP_TABLE)
|
||||
ha_info->set_trx_read_write();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Repair table: public interface.
|
||||
@ -2516,6 +2948,9 @@ int handler::ha_check(THD *thd, HA_CHECK_OPT *check_opt)
|
||||
int handler::ha_repair(THD* thd, HA_CHECK_OPT* check_opt)
|
||||
{
|
||||
int result;
|
||||
|
||||
mark_trx_read_write();
|
||||
|
||||
if ((result= repair(thd, check_opt)))
|
||||
return result;
|
||||
return update_frm_version(table);
|
||||
@ -2532,6 +2967,8 @@ int
|
||||
handler::ha_bulk_update_row(const uchar *old_data, uchar *new_data,
|
||||
uint *dup_key_found)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return bulk_update_row(old_data, new_data, dup_key_found);
|
||||
}
|
||||
|
||||
@ -2545,6 +2982,8 @@ handler::ha_bulk_update_row(const uchar *old_data, uchar *new_data,
|
||||
int
|
||||
handler::ha_delete_all_rows()
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return delete_all_rows();
|
||||
}
|
||||
|
||||
@ -2558,6 +2997,8 @@ handler::ha_delete_all_rows()
|
||||
int
|
||||
handler::ha_reset_auto_increment(ulonglong value)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return reset_auto_increment(value);
|
||||
}
|
||||
|
||||
@ -2571,6 +3012,8 @@ handler::ha_reset_auto_increment(ulonglong value)
|
||||
int
|
||||
handler::ha_backup(THD* thd, HA_CHECK_OPT* check_opt)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return backup(thd, check_opt);
|
||||
}
|
||||
|
||||
@ -2584,6 +3027,8 @@ handler::ha_backup(THD* thd, HA_CHECK_OPT* check_opt)
|
||||
int
|
||||
handler::ha_restore(THD* thd, HA_CHECK_OPT* check_opt)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return restore(thd, check_opt);
|
||||
}
|
||||
|
||||
@ -2597,6 +3042,8 @@ handler::ha_restore(THD* thd, HA_CHECK_OPT* check_opt)
|
||||
int
|
||||
handler::ha_optimize(THD* thd, HA_CHECK_OPT* check_opt)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return optimize(thd, check_opt);
|
||||
}
|
||||
|
||||
@ -2610,6 +3057,8 @@ handler::ha_optimize(THD* thd, HA_CHECK_OPT* check_opt)
|
||||
int
|
||||
handler::ha_analyze(THD* thd, HA_CHECK_OPT* check_opt)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return analyze(thd, check_opt);
|
||||
}
|
||||
|
||||
@ -2623,6 +3072,8 @@ handler::ha_analyze(THD* thd, HA_CHECK_OPT* check_opt)
|
||||
bool
|
||||
handler::ha_check_and_repair(THD *thd)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return check_and_repair(thd);
|
||||
}
|
||||
|
||||
@ -2636,6 +3087,8 @@ handler::ha_check_and_repair(THD *thd)
|
||||
int
|
||||
handler::ha_disable_indexes(uint mode)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return disable_indexes(mode);
|
||||
}
|
||||
|
||||
@ -2649,6 +3102,8 @@ handler::ha_disable_indexes(uint mode)
|
||||
int
|
||||
handler::ha_enable_indexes(uint mode)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return enable_indexes(mode);
|
||||
}
|
||||
|
||||
@ -2662,6 +3117,8 @@ handler::ha_enable_indexes(uint mode)
|
||||
int
|
||||
handler::ha_discard_or_import_tablespace(my_bool discard)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return discard_or_import_tablespace(discard);
|
||||
}
|
||||
|
||||
@ -2677,6 +3134,8 @@ handler::ha_discard_or_import_tablespace(my_bool discard)
|
||||
void
|
||||
handler::ha_prepare_for_alter()
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
prepare_for_alter();
|
||||
}
|
||||
|
||||
@ -2690,6 +3149,8 @@ handler::ha_prepare_for_alter()
|
||||
int
|
||||
handler::ha_rename_table(const char *from, const char *to)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return rename_table(from, to);
|
||||
}
|
||||
|
||||
@ -2703,6 +3164,8 @@ handler::ha_rename_table(const char *from, const char *to)
|
||||
int
|
||||
handler::ha_delete_table(const char *name)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return delete_table(name);
|
||||
}
|
||||
|
||||
@ -2716,6 +3179,8 @@ handler::ha_delete_table(const char *name)
|
||||
void
|
||||
handler::ha_drop_table(const char *name)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return drop_table(name);
|
||||
}
|
||||
|
||||
@ -2729,6 +3194,8 @@ handler::ha_drop_table(const char *name)
|
||||
int
|
||||
handler::ha_create(const char *name, TABLE *form, HA_CREATE_INFO *info)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return create(name, form, info);
|
||||
}
|
||||
|
||||
@ -2743,6 +3210,8 @@ int
|
||||
handler::ha_create_handler_files(const char *name, const char *old_name,
|
||||
int action_flag, HA_CREATE_INFO *info)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return create_handler_files(name, old_name, action_flag, info);
|
||||
}
|
||||
|
||||
@ -2761,6 +3230,8 @@ handler::ha_change_partitions(HA_CREATE_INFO *create_info,
|
||||
const uchar *pack_frm_data,
|
||||
size_t pack_frm_len)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return change_partitions(create_info, path, copied, deleted,
|
||||
pack_frm_data, pack_frm_len);
|
||||
}
|
||||
@ -2775,6 +3246,8 @@ handler::ha_change_partitions(HA_CREATE_INFO *create_info,
|
||||
int
|
||||
handler::ha_drop_partitions(const char *path)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return drop_partitions(path);
|
||||
}
|
||||
|
||||
@ -2788,6 +3261,8 @@ handler::ha_drop_partitions(const char *path)
|
||||
int
|
||||
handler::ha_rename_partitions(const char *path)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return rename_partitions(path);
|
||||
}
|
||||
|
||||
@ -2801,6 +3276,8 @@ handler::ha_rename_partitions(const char *path)
|
||||
int
|
||||
handler::ha_optimize_partitions(THD *thd)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return optimize_partitions(thd);
|
||||
}
|
||||
|
||||
@ -2814,6 +3291,8 @@ handler::ha_optimize_partitions(THD *thd)
|
||||
int
|
||||
handler::ha_analyze_partitions(THD *thd)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return analyze_partitions(thd);
|
||||
}
|
||||
|
||||
@ -2827,6 +3306,8 @@ handler::ha_analyze_partitions(THD *thd)
|
||||
int
|
||||
handler::ha_check_partitions(THD *thd)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return check_partitions(thd);
|
||||
}
|
||||
|
||||
@ -2840,6 +3321,8 @@ handler::ha_check_partitions(THD *thd)
|
||||
int
|
||||
handler::ha_repair_partitions(THD *thd)
|
||||
{
|
||||
mark_trx_read_write();
|
||||
|
||||
return repair_partitions(thd);
|
||||
}
|
||||
|
||||
@ -2866,7 +3349,7 @@ int ha_enable_transaction(THD *thd, bool on)
|
||||
is an optimization hint that storage engine is free to ignore.
|
||||
So, let's commit an open transaction (if any) now.
|
||||
*/
|
||||
if (!(error= ha_commit_stmt(thd)))
|
||||
if (!(error= ha_commit_trans(thd, 0)))
|
||||
error= end_trans(thd, COMMIT);
|
||||
}
|
||||
DBUG_RETURN(error);
|
||||
@ -4042,6 +4525,9 @@ int handler::ha_write_row(uchar *buf)
|
||||
{
|
||||
int error;
|
||||
DBUG_ENTER("handler::ha_write_row");
|
||||
|
||||
mark_trx_read_write();
|
||||
|
||||
if (unlikely(error= write_row(buf)))
|
||||
DBUG_RETURN(error);
|
||||
if (unlikely(error= binlog_log_row<Write_rows_log_event>(table, 0, buf)))
|
||||
@ -4060,6 +4546,8 @@ int handler::ha_update_row(const uchar *old_data, uchar *new_data)
|
||||
*/
|
||||
DBUG_ASSERT(new_data == table->record[0]);
|
||||
|
||||
mark_trx_read_write();
|
||||
|
||||
if (unlikely(error= update_row(old_data, new_data)))
|
||||
return error;
|
||||
if (unlikely(error= binlog_log_row<Update_rows_log_event>(table, old_data, new_data)))
|
||||
@ -4070,6 +4558,9 @@ int handler::ha_update_row(const uchar *old_data, uchar *new_data)
|
||||
int handler::ha_delete_row(const uchar *buf)
|
||||
{
|
||||
int error;
|
||||
|
||||
mark_trx_read_write();
|
||||
|
||||
if (unlikely(error= delete_row(buf)))
|
||||
return error;
|
||||
if (unlikely(error= binlog_log_row<Delete_rows_log_event>(table, buf, 0)))
|
||||
|
122
sql/handler.h
122
sql/handler.h
@ -721,14 +721,14 @@ struct handlerton
|
||||
#define HTON_SUPPORT_LOG_TABLES (1 << 7) //Engine supports log tables
|
||||
#define HTON_NO_PARTITION (1 << 8) //You can not partition these tables
|
||||
|
||||
typedef struct st_thd_trans
|
||||
class Ha_trx_info;
|
||||
|
||||
struct THD_TRANS
|
||||
{
|
||||
/* number of entries in the ht[] */
|
||||
uint nht;
|
||||
/* true is not all entries in the ht[] support 2pc */
|
||||
bool no_2pc;
|
||||
/* storage engines that registered themselves for this transaction */
|
||||
handlerton *ht[MAX_HA];
|
||||
/* storage engines that registered in this transaction */
|
||||
Ha_trx_info *ha_list;
|
||||
/*
|
||||
The purpose of this flag is to keep track of non-transactional
|
||||
tables that were modified in scope of:
|
||||
@ -758,7 +758,106 @@ typedef struct st_thd_trans
|
||||
saved value.
|
||||
*/
|
||||
bool modified_non_trans_table;
|
||||
} THD_TRANS;
|
||||
|
||||
void reset() { no_2pc= FALSE; modified_non_trans_table= FALSE; }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Either statement transaction or normal transaction - related
|
||||
thread-specific storage engine data.
|
||||
|
||||
If a storage engine participates in a statement/transaction,
|
||||
an instance of this class is present in
|
||||
thd->transaction.{stmt|all}.ha_list. The addition to
|
||||
{stmt|all}.ha_list is made by trans_register_ha().
|
||||
|
||||
When it's time to commit or rollback, each element of ha_list
|
||||
is used to access storage engine's prepare()/commit()/rollback()
|
||||
methods, and also to evaluate if a full two phase commit is
|
||||
necessary.
|
||||
|
||||
@sa General description of transaction handling in handler.cc.
|
||||
*/
|
||||
|
||||
class Ha_trx_info
|
||||
{
|
||||
public:
|
||||
/** Register this storage engine in the given transaction context. */
|
||||
void register_ha(THD_TRANS *trans, handlerton *ht_arg)
|
||||
{
|
||||
DBUG_ASSERT(m_flags == 0);
|
||||
DBUG_ASSERT(m_ht == NULL);
|
||||
DBUG_ASSERT(m_next == NULL);
|
||||
|
||||
m_ht= ht_arg;
|
||||
m_flags= (int) TRX_READ_ONLY; /* Assume read-only at start. */
|
||||
|
||||
m_next= trans->ha_list;
|
||||
trans->ha_list= this;
|
||||
}
|
||||
|
||||
/** Clear, prepare for reuse. */
|
||||
void reset()
|
||||
{
|
||||
m_next= NULL;
|
||||
m_ht= NULL;
|
||||
m_flags= 0;
|
||||
}
|
||||
|
||||
Ha_trx_info() { reset(); }
|
||||
|
||||
void set_trx_read_write()
|
||||
{
|
||||
DBUG_ASSERT(is_started());
|
||||
m_flags|= (int) TRX_READ_WRITE;
|
||||
}
|
||||
bool is_trx_read_write() const
|
||||
{
|
||||
DBUG_ASSERT(is_started());
|
||||
return m_flags & (int) TRX_READ_WRITE;
|
||||
}
|
||||
bool is_started() const { return m_ht != NULL; }
|
||||
/** Mark this transaction read-write if the argument is read-write. */
|
||||
void coalesce_trx_with(const Ha_trx_info *stmt_trx)
|
||||
{
|
||||
/*
|
||||
Must be called only after the transaction has been started.
|
||||
Can be called many times, e.g. when we have many
|
||||
read-write statements in a transaction.
|
||||
*/
|
||||
DBUG_ASSERT(is_started());
|
||||
if (stmt_trx->is_trx_read_write())
|
||||
set_trx_read_write();
|
||||
}
|
||||
Ha_trx_info *next() const
|
||||
{
|
||||
DBUG_ASSERT(is_started());
|
||||
return m_next;
|
||||
}
|
||||
handlerton *ht() const
|
||||
{
|
||||
DBUG_ASSERT(is_started());
|
||||
return m_ht;
|
||||
}
|
||||
private:
|
||||
enum { TRX_READ_ONLY= 0, TRX_READ_WRITE= 1 };
|
||||
/** Auxiliary, used for ha_list management */
|
||||
Ha_trx_info *m_next;
|
||||
/**
|
||||
Although a given Ha_trx_info instance is currently always used
|
||||
for the same storage engine, 'ht' is not-NULL only when the
|
||||
corresponding storage is a part of a transaction.
|
||||
*/
|
||||
handlerton *m_ht;
|
||||
/**
|
||||
Transaction flags related to this engine.
|
||||
Not-null only if this instance is a part of transaction.
|
||||
May assume a combination of enum values above.
|
||||
*/
|
||||
uchar m_flags;
|
||||
};
|
||||
|
||||
|
||||
enum enum_tx_isolation { ISO_READ_UNCOMMITTED, ISO_READ_COMMITTED,
|
||||
ISO_REPEATABLE_READ, ISO_SERIALIZABLE};
|
||||
@ -1640,7 +1739,14 @@ protected:
|
||||
provide useful functionality.
|
||||
*/
|
||||
virtual int rename_table(const char *from, const char *to);
|
||||
/**
|
||||
Delete a table in the engine. Called for base as well as temporary
|
||||
tables.
|
||||
*/
|
||||
virtual int delete_table(const char *name);
|
||||
private:
|
||||
/* Private helpers */
|
||||
inline void mark_trx_read_write();
|
||||
private:
|
||||
/*
|
||||
Low-level primitives for storage engines. These should be
|
||||
@ -1821,9 +1927,7 @@ extern TYPELIB tx_isolation_typelib;
|
||||
extern TYPELIB myisam_stats_method_typelib;
|
||||
extern ulong total_ha, total_ha_2pc;
|
||||
|
||||
/* Wrapper functions */
|
||||
#define ha_commit_stmt(thd) (ha_commit_trans((thd), FALSE))
|
||||
#define ha_rollback_stmt(thd) (ha_rollback_trans((thd), FALSE))
|
||||
/* Wrapper functions */
|
||||
#define ha_commit(thd) (ha_commit_trans((thd), TRUE))
|
||||
#define ha_rollback(thd) (ha_rollback_trans((thd), TRUE))
|
||||
|
||||
|
10
sql/log.cc
10
sql/log.cc
@ -3332,6 +3332,16 @@ THD::binlog_start_trans_and_stmt()
|
||||
if (options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))
|
||||
trans_register_ha(this, TRUE, binlog_hton);
|
||||
trans_register_ha(this, FALSE, binlog_hton);
|
||||
/*
|
||||
Mark statement transaction as read/write. We never start
|
||||
a binary log transaction and keep it read-only,
|
||||
therefore it's best to mark the transaction read/write just
|
||||
at the same time we start it.
|
||||
Not necessary to mark the normal transaction read/write
|
||||
since the statement-level flag will be propagated automatically
|
||||
inside ha_commit_trans.
|
||||
*/
|
||||
ha_data[binlog_hton->slot].ha_info[0].set_trx_read_write();
|
||||
}
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
@ -2898,7 +2898,7 @@ int Format_description_log_event::do_apply_event(Relay_log_info const *rli)
|
||||
original place when it comes to us; we'll know this by checking
|
||||
log_pos ("artificial" events have log_pos == 0).
|
||||
*/
|
||||
if (!artificial_event && created && thd->transaction.all.nht)
|
||||
if (!artificial_event && created && thd->transaction.all.ha_list)
|
||||
{
|
||||
/* This is not an error (XA is safe), just an information */
|
||||
rli->report(INFORMATION_LEVEL, 0,
|
||||
|
@ -62,6 +62,26 @@ int injector::transaction::commit()
|
||||
{
|
||||
DBUG_ENTER("injector::transaction::commit()");
|
||||
m_thd->binlog_flush_pending_rows_event(true);
|
||||
/*
|
||||
Cluster replication does not preserve statement or
|
||||
transaction boundaries of the master. Instead, a new
|
||||
transaction on replication slave is started when a new GCI
|
||||
(global checkpoint identifier) is issued, and is committed
|
||||
when the last event of the check point has been received and
|
||||
processed. This ensures consistency of each cluster in
|
||||
cluster replication, and there is no requirement for stronger
|
||||
consistency: MySQL replication is asynchronous with other
|
||||
engines as well.
|
||||
|
||||
A practical consequence of that is that row level replication
|
||||
stream passed through the injector thread never contains
|
||||
COMMIT events.
|
||||
Here we should preserve the server invariant that there is no
|
||||
outstanding statement transaction when the normal transaction
|
||||
is committed by committing the statement transaction
|
||||
explicitly.
|
||||
*/
|
||||
ha_autocommit_or_rollback(m_thd, 0);
|
||||
end_trans(m_thd, COMMIT);
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
20
sql/sp.cc
20
sql/sp.cc
@ -665,8 +665,16 @@ sp_returns_type(THD *thd, String &result, sp_head *sp)
|
||||
(TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION).
|
||||
@param sp Stored routine object to store.
|
||||
|
||||
@return Error code. SP_OK is returned on success. Other SP_ constants are
|
||||
used to indicate about errors.
|
||||
@note Opens and closes the thread tables. Therefore assumes
|
||||
that there are no locked tables in this thread at the time of
|
||||
invocation.
|
||||
Unlike some other DDL statements, *does* close the tables
|
||||
in the end, since the call to this function is normally
|
||||
followed by an implicit grant (sp_grant_privileges())
|
||||
and this subsequent call opens and closes mysql.procs_priv.
|
||||
|
||||
@return Error code. SP_OK is returned on success. Other
|
||||
SP_ constants are used to indicate about errors.
|
||||
*/
|
||||
|
||||
int
|
||||
@ -1223,7 +1231,13 @@ done:
|
||||
}
|
||||
|
||||
|
||||
/* Drop all routines in database 'db' */
|
||||
/**
|
||||
Drop all routines in database 'db'
|
||||
|
||||
@note Close the thread tables, the calling code might want to
|
||||
delete from other system tables afterwards.
|
||||
*/
|
||||
|
||||
int
|
||||
sp_drop_db_routines(THD *thd, char *db)
|
||||
{
|
||||
|
@ -2700,6 +2700,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
|
||||
m_lex->unit.cleanup();
|
||||
|
||||
thd_proc_info(thd, "closing tables");
|
||||
/* Here we also commit or rollback the current statement. */
|
||||
close_thread_tables(thd);
|
||||
thd_proc_info(thd, 0);
|
||||
|
||||
|
@ -1324,29 +1324,45 @@ void close_thread_tables(THD *thd)
|
||||
Mark all temporary tables used by this statement as free for reuse.
|
||||
*/
|
||||
mark_temp_tables_as_free_for_reuse(thd);
|
||||
/*
|
||||
Let us commit transaction for statement. Since in 5.0 we only have
|
||||
one statement transaction and don't allow several nested statement
|
||||
transactions this call will do nothing if we are inside of stored
|
||||
function or trigger (i.e. statement transaction is already active and
|
||||
does not belong to statement for which we do close_thread_tables()).
|
||||
TODO: This should be fixed in later releases.
|
||||
*/
|
||||
if (!(thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
|
||||
{
|
||||
thd->main_da.can_overwrite_status= TRUE;
|
||||
ha_autocommit_or_rollback(thd, thd->is_error());
|
||||
thd->main_da.can_overwrite_status= FALSE;
|
||||
|
||||
/*
|
||||
Reset transaction state, but only if we're not inside a
|
||||
sub-statement of a prelocked statement.
|
||||
*/
|
||||
if (! prelocked_mode || thd->lex->requires_prelocking())
|
||||
thd->transaction.stmt.reset();
|
||||
}
|
||||
|
||||
if (thd->locked_tables || prelocked_mode)
|
||||
{
|
||||
/*
|
||||
Let us commit transaction for statement. Since in 5.0 we only have
|
||||
one statement transaction and don't allow several nested statement
|
||||
transactions this call will do nothing if we are inside of stored
|
||||
function or trigger (i.e. statement transaction is already active and
|
||||
does not belong to statement for which we do close_thread_tables()).
|
||||
TODO: This should be fixed in later releases.
|
||||
*/
|
||||
ha_commit_stmt(thd);
|
||||
|
||||
/* Ensure we are calling ha_reset() for all used tables */
|
||||
mark_used_tables_as_free_for_reuse(thd, thd->open_tables);
|
||||
|
||||
/* We are under simple LOCK TABLES so should not do anything else. */
|
||||
/*
|
||||
We are under simple LOCK TABLES or we're inside a sub-statement
|
||||
of a prelocked statement, so should not do anything else.
|
||||
*/
|
||||
if (!prelocked_mode || !thd->lex->requires_prelocking())
|
||||
DBUG_VOID_RETURN;
|
||||
|
||||
/*
|
||||
We are in prelocked mode, so we have to leave it now with doing
|
||||
implicit UNLOCK TABLES if need.
|
||||
We are in the top-level statement of a prelocked statement,
|
||||
so we have to leave the prelocked mode now with doing implicit
|
||||
UNLOCK TABLES if needed.
|
||||
*/
|
||||
DBUG_PRINT("info",("thd->prelocked_mode= NON_PRELOCKED"));
|
||||
thd->prelocked_mode= NON_PRELOCKED;
|
||||
@ -1374,19 +1390,6 @@ void close_thread_tables(THD *thd)
|
||||
mysql_unlock_tables(thd, thd->lock);
|
||||
thd->lock=0;
|
||||
}
|
||||
/*
|
||||
assume handlers auto-commit (if some doesn't - transaction handling
|
||||
in MySQL should be redesigned to support it; it's a big change,
|
||||
and it's not worth it - better to commit explicitly only writing
|
||||
transactions, read-only ones should better take care of themselves.
|
||||
saves some work in 2pc too)
|
||||
see also sql_parse.cc - dispatch_command()
|
||||
*/
|
||||
if (!(thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
|
||||
bzero(&thd->transaction.stmt, sizeof(thd->transaction.stmt));
|
||||
if (!thd->active_transaction())
|
||||
thd->transaction.xid_state.xid.null();
|
||||
|
||||
/*
|
||||
Note that we need to hold LOCK_open while changing the
|
||||
open_tables list. Another thread may work on it.
|
||||
@ -5059,10 +5062,7 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables)
|
||||
DBUG_PRINT("info", ("error: %d", error));
|
||||
|
||||
if (error)
|
||||
{
|
||||
ha_rollback_stmt(thd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
We switch to row-based format if we are in mixed mode and one of
|
||||
@ -5216,7 +5216,6 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
|
||||
table->table->query_id= thd->query_id;
|
||||
if (check_lock_and_start_stmt(thd, table->table, table->lock_type))
|
||||
{
|
||||
ha_rollback_stmt(thd);
|
||||
mysql_unlock_tables(thd, thd->locked_tables);
|
||||
thd->locked_tables= 0;
|
||||
thd->options&= ~(OPTION_TABLE_LOCK);
|
||||
@ -5251,7 +5250,6 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
|
||||
if (!table->placeholder() &&
|
||||
check_lock_and_start_stmt(thd, table->table, table->lock_type))
|
||||
{
|
||||
ha_rollback_stmt(thd);
|
||||
DBUG_RETURN(-1);
|
||||
}
|
||||
}
|
||||
|
@ -264,7 +264,7 @@ const char *set_thd_proc_info(THD *thd, const char *info,
|
||||
extern "C"
|
||||
void **thd_ha_data(const THD *thd, const struct handlerton *hton)
|
||||
{
|
||||
return (void **) thd->ha_data + hton->slot;
|
||||
return (void **) &thd->ha_data[hton->slot].ha_ptr;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
@ -2513,7 +2513,7 @@ bool select_dumpvar::send_data(List<Item> &items)
|
||||
suv->update();
|
||||
}
|
||||
}
|
||||
DBUG_RETURN(0);
|
||||
DBUG_RETURN(thd->is_error());
|
||||
}
|
||||
|
||||
bool select_dumpvar::send_eof()
|
||||
|
@ -686,7 +686,8 @@ private:
|
||||
struct st_savepoint {
|
||||
struct st_savepoint *prev;
|
||||
char *name;
|
||||
uint length, nht;
|
||||
uint length;
|
||||
Ha_trx_info *ha_list;
|
||||
};
|
||||
|
||||
enum xa_states {XA_NOTR=0, XA_ACTIVE, XA_IDLE, XA_PREPARED};
|
||||
@ -1092,6 +1093,33 @@ private:
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Storage engine specific thread local data.
|
||||
*/
|
||||
|
||||
struct Ha_data
|
||||
{
|
||||
/**
|
||||
Storage engine specific thread local data.
|
||||
Lifetime: one user connection.
|
||||
*/
|
||||
void *ha_ptr;
|
||||
/**
|
||||
0: Life time: one statement within a transaction. If @@autocommit is
|
||||
on, also represents the entire transaction.
|
||||
@sa trans_register_ha()
|
||||
|
||||
1: Life time: one transaction within a connection.
|
||||
If the storage engine does not participate in a transaction,
|
||||
this should not be used.
|
||||
@sa trans_register_ha()
|
||||
*/
|
||||
Ha_trx_info ha_info[2];
|
||||
|
||||
Ha_data() :ha_ptr(NULL) {}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
@class THD
|
||||
For each client connection we create a separate thread with THD serving as
|
||||
@ -1231,7 +1259,7 @@ public:
|
||||
uint in_sub_stmt;
|
||||
|
||||
/* container for handler's private per-connection data */
|
||||
void *ha_data[MAX_HA];
|
||||
Ha_data ha_data[MAX_HA];
|
||||
|
||||
#ifndef MYSQL_CLIENT
|
||||
int binlog_setup_trx_data();
|
||||
|
@ -322,9 +322,10 @@ Sensitive_cursor::post_open(THD *thd)
|
||||
|
||||
close_at_commit= FALSE; /* reset in case we're reusing the cursor */
|
||||
info= &ht_info[0];
|
||||
for (handlerton **pht= thd->transaction.stmt.ht; *pht; pht++)
|
||||
for (Ha_trx_info *ha_trx_info= thd->transaction.stmt.ha_list;
|
||||
ha_trx_info; ha_trx_info= ha_trx_info->next())
|
||||
{
|
||||
handlerton *ht= *pht;
|
||||
handlerton *ht= ha_trx_info->ht();
|
||||
close_at_commit|= test(ht->flags & HTON_CLOSE_CURSORS_AT_COMMIT);
|
||||
if (ht->create_cursor_read_view)
|
||||
{
|
||||
|
@ -24,6 +24,14 @@
|
||||
#include "sp_head.h"
|
||||
#include "sql_trigger.h"
|
||||
|
||||
/**
|
||||
Implement DELETE SQL word.
|
||||
|
||||
@note Like implementations of other DDL/DML in MySQL, this function
|
||||
relies on the caller to close the thread tables. This is done in the
|
||||
end of dispatch_command().
|
||||
*/
|
||||
|
||||
bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
|
||||
SQL_LIST *order, ha_rows limit, ulonglong options,
|
||||
bool reset_auto_increment)
|
||||
@ -380,17 +388,6 @@ cleanup:
|
||||
}
|
||||
DBUG_ASSERT(transactional_table || !deleted || thd->transaction.stmt.modified_non_trans_table);
|
||||
free_underlaid_joins(thd, select_lex);
|
||||
if (transactional_table)
|
||||
{
|
||||
if (ha_autocommit_or_rollback(thd,error >= 0))
|
||||
error=1;
|
||||
}
|
||||
|
||||
if (thd->lock)
|
||||
{
|
||||
mysql_unlock_tables(thd, thd->lock);
|
||||
thd->lock=0;
|
||||
}
|
||||
if (error < 0 || (thd->lex->ignore && !thd->is_fatal_error))
|
||||
{
|
||||
thd->row_count_func= deleted;
|
||||
@ -751,11 +748,9 @@ void multi_delete::abort()
|
||||
The same if all tables are transactional, regardless of where we are.
|
||||
In all other cases do attempt deletes ...
|
||||
*/
|
||||
if ((table_being_deleted == delete_tables &&
|
||||
table_being_deleted->table->file->has_transactions()) ||
|
||||
!normal_tables)
|
||||
ha_rollback_stmt(thd);
|
||||
else if (do_delete)
|
||||
if (do_delete && normal_tables &&
|
||||
(table_being_deleted != delete_tables ||
|
||||
!table_being_deleted->table->file->has_transactions()))
|
||||
{
|
||||
/*
|
||||
We have to execute the recorded do_deletes() and write info into the
|
||||
@ -921,11 +916,6 @@ bool multi_delete::send_eof()
|
||||
if (local_error != 0)
|
||||
error_handled= TRUE; // to force early leave from ::send_error()
|
||||
|
||||
/* Commit or rollback the current SQL statement */
|
||||
if (transactional_tables)
|
||||
if (ha_autocommit_or_rollback(thd,local_error > 0))
|
||||
local_error=1;
|
||||
|
||||
if (!local_error)
|
||||
{
|
||||
thd->row_count_func= deleted;
|
||||
@ -1055,6 +1045,12 @@ trunc_by_del:
|
||||
error= mysql_delete(thd, table_list, (COND*) 0, (SQL_LIST*) 0,
|
||||
HA_POS_ERROR, LL(0), TRUE);
|
||||
ha_enable_transaction(thd, TRUE);
|
||||
/*
|
||||
Safety, in case the engine ignored ha_enable_transaction(FALSE)
|
||||
above. Also clears thd->transaction.*.
|
||||
*/
|
||||
error= ha_autocommit_or_rollback(thd, error);
|
||||
ha_commit(thd);
|
||||
thd->options= save_options;
|
||||
thd->current_stmt_binlog_row_based= save_binlog_row_based;
|
||||
DBUG_RETURN(error);
|
||||
|
@ -28,7 +28,17 @@ bool mysql_do(THD *thd, List<Item> &values)
|
||||
while ((value = li++))
|
||||
value->val_int();
|
||||
free_underlaid_joins(thd, &thd->lex->select_lex);
|
||||
thd->clear_error(); // DO always is OK
|
||||
|
||||
if (thd->is_error())
|
||||
{
|
||||
/*
|
||||
Rollback the effect of the statement, since next instruction
|
||||
will clear the error and the rollback in the end of
|
||||
dispatch_command() won't work.
|
||||
*/
|
||||
ha_autocommit_or_rollback(thd, thd->is_error());
|
||||
thd->clear_error(); // DO always is OK
|
||||
}
|
||||
send_ok(thd);
|
||||
DBUG_RETURN(FALSE);
|
||||
}
|
||||
|
@ -541,6 +541,10 @@ bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list)
|
||||
|
||||
/**
|
||||
INSERT statement implementation
|
||||
|
||||
@note Like implementations of other DDL/DML in MySQL, this function
|
||||
relies on the caller to close the thread tables. This is done in the
|
||||
end of dispatch_command().
|
||||
*/
|
||||
|
||||
bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
||||
@ -893,12 +897,9 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
||||
}
|
||||
DBUG_ASSERT(transactional_table || !changed ||
|
||||
thd->transaction.stmt.modified_non_trans_table);
|
||||
if (transactional_table)
|
||||
error=ha_autocommit_or_rollback(thd,error);
|
||||
|
||||
|
||||
if (thd->lock)
|
||||
{
|
||||
mysql_unlock_tables(thd, thd->lock);
|
||||
/*
|
||||
Invalidate the table in the query cache if something changed
|
||||
after unlocking when changes become fisible.
|
||||
@ -909,7 +910,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
||||
{
|
||||
query_cache_invalidate3(thd, table_list, 1);
|
||||
}
|
||||
thd->lock=0;
|
||||
}
|
||||
}
|
||||
thd_proc_info(thd, "end");
|
||||
@ -2445,7 +2445,7 @@ err:
|
||||
first call to ha_*_row() instead. Remove code that are used to
|
||||
cover for the case outlined above.
|
||||
*/
|
||||
ha_rollback_stmt(thd);
|
||||
ha_autocommit_or_rollback(thd, 1);
|
||||
|
||||
#ifndef __WIN__
|
||||
end:
|
||||
@ -3139,18 +3139,6 @@ bool select_insert::send_eof()
|
||||
thd->query, thd->query_length,
|
||||
trans_table, FALSE, killed_status);
|
||||
}
|
||||
/*
|
||||
We will call ha_autocommit_or_rollback() also for
|
||||
non-transactional tables under row-based replication: there might
|
||||
be events in the binary logs transaction, and we need to write
|
||||
them to the binary log.
|
||||
*/
|
||||
if (trans_table || thd->current_stmt_binlog_row_based)
|
||||
{
|
||||
int error2= ha_autocommit_or_rollback(thd, error);
|
||||
if (error2 && !error)
|
||||
error= error2;
|
||||
}
|
||||
table->file->ha_release_auto_increment();
|
||||
|
||||
if (error)
|
||||
@ -3228,7 +3216,6 @@ void select_insert::abort() {
|
||||
table->file->ha_release_auto_increment();
|
||||
}
|
||||
|
||||
ha_rollback_stmt(thd);
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
@ -3667,7 +3654,10 @@ bool select_create::send_eof()
|
||||
nevertheless.
|
||||
*/
|
||||
if (!table->s->tmp_table)
|
||||
ha_commit(thd); // Can fail, but we proceed anyway
|
||||
{
|
||||
ha_autocommit_or_rollback(thd, 0);
|
||||
end_active_trans(thd);
|
||||
}
|
||||
|
||||
table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
|
||||
table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
|
||||
@ -3691,12 +3681,9 @@ void select_create::abort()
|
||||
by removing the table, even for non-transactional tables.
|
||||
*/
|
||||
tmp_disable_binlog(thd);
|
||||
select_insert::abort();
|
||||
reenable_binlog(thd);
|
||||
|
||||
/*
|
||||
We roll back the statement, including truncating the transaction
|
||||
cache of the binary log, if the statement failed.
|
||||
In select_insert::abort() we roll back the statement, including
|
||||
truncating the transaction cache of the binary log.
|
||||
|
||||
We roll back the statement prior to deleting the table and prior
|
||||
to releasing the lock on the table, since there might be potential
|
||||
@ -3707,8 +3694,9 @@ void select_create::abort()
|
||||
of the table succeeded or not, since we need to reset the binary
|
||||
log state.
|
||||
*/
|
||||
if (thd->current_stmt_binlog_row_based)
|
||||
ha_rollback_stmt(thd);
|
||||
select_insert::abort();
|
||||
reenable_binlog(thd);
|
||||
|
||||
|
||||
if (m_plock)
|
||||
{
|
||||
|
@ -470,9 +470,6 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list,
|
||||
}
|
||||
}
|
||||
#endif /*!EMBEDDED_LIBRARY*/
|
||||
if (transactional_table)
|
||||
ha_autocommit_or_rollback(thd,error);
|
||||
|
||||
error= -1; // Error on read
|
||||
goto err;
|
||||
}
|
||||
@ -510,8 +507,6 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list,
|
||||
}
|
||||
}
|
||||
#endif /*!EMBEDDED_LIBRARY*/
|
||||
if (transactional_table)
|
||||
error=ha_autocommit_or_rollback(thd,error);
|
||||
|
||||
/* ok to client sent only after binlog write and engine commit */
|
||||
send_ok(thd, info.copied + info.deleted, 0L, name);
|
||||
@ -519,11 +514,6 @@ err:
|
||||
DBUG_ASSERT(transactional_table || !(info.copied || info.deleted) ||
|
||||
thd->transaction.stmt.modified_non_trans_table);
|
||||
table->file->ha_release_auto_increment();
|
||||
if (thd->lock)
|
||||
{
|
||||
mysql_unlock_tables(thd, thd->lock);
|
||||
thd->lock=0;
|
||||
}
|
||||
table->auto_increment_field_not_null= FALSE;
|
||||
thd->abort_on_warning= 0;
|
||||
DBUG_RETURN(error);
|
||||
|
@ -1465,21 +1465,13 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
|
||||
break;
|
||||
}
|
||||
|
||||
thd_proc_info(thd, "closing tables");
|
||||
/* Free tables */
|
||||
close_thread_tables(thd);
|
||||
/* If commit fails, we should be able to reset the OK status. */
|
||||
thd->main_da.can_overwrite_status= TRUE;
|
||||
ha_autocommit_or_rollback(thd, thd->is_error());
|
||||
thd->main_da.can_overwrite_status= FALSE;
|
||||
|
||||
thd->transaction.stmt.reset();
|
||||
|
||||
/*
|
||||
assume handlers auto-commit (if some doesn't - transaction handling
|
||||
in MySQL should be redesigned to support it; it's a big change,
|
||||
and it's not worth it - better to commit explicitly only writing
|
||||
transactions, read-only ones should better take care of themselves.
|
||||
saves some work in 2pc too)
|
||||
see also sql_base.cc - close_thread_tables()
|
||||
*/
|
||||
bzero(&thd->transaction.stmt, sizeof(thd->transaction.stmt));
|
||||
if (!thd->active_transaction())
|
||||
thd->transaction.xid_state.xid.null();
|
||||
|
||||
/* report error issued during command execution */
|
||||
if (thd->killed_errno())
|
||||
@ -1496,6 +1488,10 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
|
||||
net_end_statement(thd);
|
||||
query_cache_end_of_result(thd);
|
||||
|
||||
thd->proc_info= "closing tables";
|
||||
/* Free tables */
|
||||
close_thread_tables(thd);
|
||||
|
||||
log_slow_statement(thd);
|
||||
|
||||
thd_proc_info(thd, "cleaning up");
|
||||
@ -3011,10 +3007,8 @@ end_with_restore_list:
|
||||
/* INSERT ... SELECT should invalidate only the very first table */
|
||||
TABLE_LIST *save_table= first_table->next_local;
|
||||
first_table->next_local= 0;
|
||||
mysql_unlock_tables(thd, thd->lock);
|
||||
query_cache_invalidate3(thd, first_table, 1);
|
||||
first_table->next_local= save_table;
|
||||
thd->lock=0;
|
||||
}
|
||||
delete sel_result;
|
||||
}
|
||||
@ -3985,7 +3979,6 @@ end_with_restore_list:
|
||||
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
|
||||
ER_PROC_AUTO_GRANT_FAIL,
|
||||
ER(ER_PROC_AUTO_GRANT_FAIL));
|
||||
close_thread_tables(thd);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
|
@ -3968,31 +3968,35 @@ static int fast_end_partition(THD *thd, ulonglong copied,
|
||||
bool written_bin_log)
|
||||
{
|
||||
int error;
|
||||
char tmp_name[80];
|
||||
DBUG_ENTER("fast_end_partition");
|
||||
|
||||
thd->proc_info="end";
|
||||
|
||||
if (!is_empty)
|
||||
query_cache_invalidate3(thd, table_list, 0);
|
||||
error= ha_commit_stmt(thd);
|
||||
if (ha_commit(thd))
|
||||
|
||||
error= ha_autocommit_or_rollback(thd, 0);
|
||||
if (end_active_trans(thd))
|
||||
error= 1;
|
||||
if (!error || is_empty)
|
||||
|
||||
if (error)
|
||||
{
|
||||
char tmp_name[80];
|
||||
if ((!is_empty) && (!written_bin_log) &&
|
||||
(!thd->lex->no_write_to_binlog))
|
||||
write_bin_log(thd, FALSE, thd->query, thd->query_length);
|
||||
close_thread_tables(thd);
|
||||
my_snprintf(tmp_name, sizeof(tmp_name), ER(ER_INSERT_INFO),
|
||||
(ulong) (copied + deleted),
|
||||
(ulong) deleted,
|
||||
(ulong) 0);
|
||||
send_ok(thd, (ha_rows) (copied+deleted),0L,tmp_name);
|
||||
DBUG_RETURN(FALSE);
|
||||
/* If error during commit, no need to rollback, it's done. */
|
||||
table->file->print_error(error, MYF(0));
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
table->file->print_error(error, MYF(0));
|
||||
close_thread_tables(thd);
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
if ((!is_empty) && (!written_bin_log) &&
|
||||
(!thd->lex->no_write_to_binlog))
|
||||
write_bin_log(thd, FALSE, thd->query, thd->query_length);
|
||||
|
||||
my_snprintf(tmp_name, sizeof(tmp_name), ER(ER_INSERT_INFO),
|
||||
(ulong) (copied + deleted),
|
||||
(ulong) deleted,
|
||||
(ulong) 0);
|
||||
send_ok(thd, (ha_rows) (copied+deleted),0L, tmp_name);
|
||||
DBUG_RETURN(FALSE);
|
||||
}
|
||||
|
||||
|
||||
|
@ -4131,6 +4131,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
||||
switch ((*prepare_func)(thd, table, check_opt)) {
|
||||
case 1: // error, message written to net
|
||||
ha_autocommit_or_rollback(thd, 1);
|
||||
end_trans(thd, ROLLBACK);
|
||||
close_thread_tables(thd);
|
||||
DBUG_PRINT("admin", ("simple error, admin next table"));
|
||||
continue;
|
||||
@ -4189,6 +4190,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
||||
table_name);
|
||||
protocol->store(buff, length, system_charset_info);
|
||||
ha_autocommit_or_rollback(thd, 0);
|
||||
end_trans(thd, COMMIT);
|
||||
close_thread_tables(thd);
|
||||
lex->reset_query_tables_list(FALSE);
|
||||
table->table=0; // For query cache
|
||||
@ -4461,6 +4463,7 @@ send_result_message:
|
||||
}
|
||||
}
|
||||
ha_autocommit_or_rollback(thd, 0);
|
||||
end_trans(thd, COMMIT);
|
||||
close_thread_tables(thd);
|
||||
table->table=0; // For query cache
|
||||
if (protocol->write())
|
||||
@ -4470,8 +4473,9 @@ send_result_message:
|
||||
send_eof(thd);
|
||||
DBUG_RETURN(FALSE);
|
||||
|
||||
err:
|
||||
err:
|
||||
ha_autocommit_or_rollback(thd, 1);
|
||||
end_trans(thd, ROLLBACK);
|
||||
close_thread_tables(thd); // Shouldn't be needed
|
||||
if (table)
|
||||
table->table=0;
|
||||
@ -4994,8 +4998,8 @@ mysql_discard_or_import_tablespace(THD *thd,
|
||||
query_cache_invalidate3(thd, table_list, 0);
|
||||
|
||||
/* The ALTER TABLE is always in its own transaction */
|
||||
error = ha_commit_stmt(thd);
|
||||
if (ha_commit(thd))
|
||||
error = ha_autocommit_or_rollback(thd, 0);
|
||||
if (end_active_trans(thd))
|
||||
error=1;
|
||||
if (error)
|
||||
goto err;
|
||||
@ -5003,7 +5007,6 @@ mysql_discard_or_import_tablespace(THD *thd,
|
||||
|
||||
err:
|
||||
ha_autocommit_or_rollback(thd, error);
|
||||
close_thread_tables(thd);
|
||||
thd->tablespace_op=FALSE;
|
||||
|
||||
if (error == 0)
|
||||
@ -6526,8 +6529,8 @@ view_err:
|
||||
VOID(pthread_mutex_unlock(&LOCK_open));
|
||||
alter_table_manage_keys(table, table->file->indexes_are_disabled(),
|
||||
alter_info->keys_onoff);
|
||||
error= ha_commit_stmt(thd);
|
||||
if (ha_commit(thd))
|
||||
error= ha_autocommit_or_rollback(thd, 0);
|
||||
if (end_active_trans(thd))
|
||||
error= 1;
|
||||
}
|
||||
thd->count_cuted_fields= CHECK_FIELD_IGNORE;
|
||||
@ -6615,7 +6618,7 @@ view_err:
|
||||
|
||||
/* Need to commit before a table is unlocked (NDB requirement). */
|
||||
DBUG_PRINT("info", ("Committing before unlocking table"));
|
||||
if (ha_commit_stmt(thd) || ha_commit(thd))
|
||||
if (ha_autocommit_or_rollback(thd, 0) || end_active_trans(thd))
|
||||
goto err1;
|
||||
committed= 1;
|
||||
}
|
||||
@ -7116,9 +7119,9 @@ copy_data_between_tables(TABLE *from,TABLE *to,
|
||||
Ensure that the new table is saved properly to disk so that we
|
||||
can do a rename
|
||||
*/
|
||||
if (ha_commit_stmt(thd))
|
||||
if (ha_autocommit_or_rollback(thd, 0))
|
||||
error=1;
|
||||
if (ha_commit(thd))
|
||||
if (end_active_trans(thd))
|
||||
error=1;
|
||||
|
||||
err:
|
||||
|
@ -382,6 +382,14 @@ static udf_func *add_udf(LEX_STRING *name, Item_result ret, char *dl,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Create a user defined function.
|
||||
|
||||
@note Like implementations of other DDL/DML in MySQL, this function
|
||||
relies on the caller to close the thread tables. This is done in the
|
||||
end of dispatch_command().
|
||||
*/
|
||||
|
||||
int mysql_create_function(THD *thd,udf_func *udf)
|
||||
{
|
||||
int error;
|
||||
@ -489,7 +497,6 @@ int mysql_create_function(THD *thd,udf_func *udf)
|
||||
table->field[3]->store((longlong) u_d->type, TRUE);
|
||||
error = table->file->ha_write_row(table->record[0]);
|
||||
|
||||
close_thread_tables(thd);
|
||||
if (error)
|
||||
{
|
||||
my_error(ER_ERROR_ON_WRITE, MYF(0), "mysql.func", error);
|
||||
|
@ -803,17 +803,6 @@ int mysql_update(THD *thd,
|
||||
}
|
||||
DBUG_ASSERT(transactional_table || !updated || thd->transaction.stmt.modified_non_trans_table);
|
||||
free_underlaid_joins(thd, select_lex);
|
||||
if (transactional_table)
|
||||
{
|
||||
if (ha_autocommit_or_rollback(thd, error >= 0))
|
||||
error=1;
|
||||
}
|
||||
|
||||
if (thd->lock)
|
||||
{
|
||||
mysql_unlock_tables(thd, thd->lock);
|
||||
thd->lock=0;
|
||||
}
|
||||
|
||||
/* If LAST_INSERT_ID(X) was used, report X */
|
||||
id= thd->arg_of_last_insert_id_function ?
|
||||
@ -1716,13 +1705,8 @@ void multi_update::abort()
|
||||
If not attempt to do remaining updates.
|
||||
*/
|
||||
|
||||
if (trans_safe)
|
||||
if (! trans_safe)
|
||||
{
|
||||
DBUG_ASSERT(transactional_tables);
|
||||
(void) ha_autocommit_or_rollback(thd, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
DBUG_ASSERT(thd->transaction.stmt.modified_non_trans_table);
|
||||
if (do_update && table_count > 1)
|
||||
{
|
||||
@ -1754,11 +1738,6 @@ void multi_update::abort()
|
||||
thd->transaction.all.modified_non_trans_table= TRUE;
|
||||
}
|
||||
DBUG_ASSERT(trans_safe || !updated || thd->transaction.stmt.modified_non_trans_table);
|
||||
|
||||
if (transactional_tables)
|
||||
{
|
||||
(void) ha_autocommit_or_rollback(thd, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1996,12 +1975,6 @@ bool multi_update::send_eof()
|
||||
if (local_error != 0)
|
||||
error_handled= TRUE; // to force early leave from ::send_error()
|
||||
|
||||
if (transactional_tables)
|
||||
{
|
||||
if (ha_autocommit_or_rollback(thd, local_error != 0))
|
||||
local_error=1;
|
||||
}
|
||||
|
||||
if (local_error > 0) // if the above log write did not fail ...
|
||||
{
|
||||
/* Safety: If we haven't got an error before (can happen in do_updates) */
|
||||
|
Loading…
x
Reference in New Issue
Block a user