Commit 58b98078 authored by Sujatha Sivakumar's avatar Sujatha Sivakumar

Bug#17942050:KILL OF TRUNCATE TABLE WILL LEAD TO BINARY LOG

WRITTEN WHILE ROWS REMAINS

Problem:
========
When truncate table fails while using transactional based
engines even though the operation errors out we still
continue and log it to binlog. Because of this master has
data but the truncate will be written to binary log which
will cause inconsistency.

Analysis:
========
Truncate table can happen either through drop and create of
table or by deleting rows. In the second case the existing
code is written in such a way that even if an error occurs
the truncate statement will always be binlogged. Which is not
correct.

Binlogging of TRUNCATE TABLE statement should check whether
truncate is executed "transactionally or not". If the table
is transaction based we log the TRUNCATE TABLE only on
successful completion.

If table is non transactional there are possibilities that on
error we could have partial changes done hence in such cases
we do log in spite of errors as some of the lines might have
been removed, so the statement has to be sent to slave.

Fix:
===
Using table handler whether truncate table is being executed
in transaction based mode or not is identified and statement
is binlogged accordingly.
parent db2403cd
RESET MASTER;
connection default;
CREATE TABLE t1(id INT AUTO_INCREMENT PRIMARY KEY, a INT, b INT) ENGINE=INNODB;
INSERT INTO t1(a, b) VALUES(1,2),(2,4),(3,6),(4,8),(5,10);
SET DEBUG_SYNC = "open_and_process_table signal truncate_before_lock wait_for forever";
TRUNCATE t1;
connect con1,localhost,root,,;
SET DEBUG_SYNC = "now wait_for truncate_before_lock";
SELECT ((@id := id) - id) FROM information_schema.processlist WHERE processlist.info LIKE '%TRUNCATE t1%' AND state LIKE '%open_and_process_table%';
((@id := id) - id)
0
KILL QUERY @id;
connection default;
ERROR 70100: Query execution was interrupted
connection con1;
show binlog events from <binlog_start>;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Query # # use `test`; CREATE TABLE t1(id INT AUTO_INCREMENT PRIMARY KEY, a INT, b INT) ENGINE=INNODB
master-bin.000001 # Query # # BEGIN
master-bin.000001 # Intvar # # INSERT_ID=1
master-bin.000001 # Query # # use `test`; INSERT INTO t1(a, b) VALUES(1,2),(2,4),(3,6),(4,8),(5,10)
master-bin.000001 # Xid # # COMMIT /* XID */
disconnect con1;
connection default;
SELECT * FROM t1;
id a b
1 1 2
2 2 4
3 3 6
4 4 8
5 5 10
DROP TABLE t1;
SET DEBUG_SYNC= 'RESET';
###############################################################################
# Bug#17942050:KILL OF TRUNCATE TABLE WILL LEAD TO BINARY LOG WRITTEN WHILE
# ROWS REMAINS
#
# Problem:
# ========
# When truncate table fails while using transactional based engines even
# though the operation errors out we still continue and log it to binlog.
# Because of this master has data but the truncate will be written to binary
# log which will cause inconsistency.
#
# Test:
# =====
# Make master to wait in "open_table" call during the execution of truncate
# table command and kill the truncate table from other connection. This causes
# open table to return an error saying truncate failed during open table. This
# statement should not be binlogged.
###############################################################################
--source include/have_debug_sync.inc
--source include/have_binlog_format_statement.inc
RESET MASTER;
--enable_connect_log
--connection default
CREATE TABLE t1(id INT AUTO_INCREMENT PRIMARY KEY, a INT, b INT) ENGINE=INNODB;
INSERT INTO t1(a, b) VALUES(1,2),(2,4),(3,6),(4,8),(5,10);
SET DEBUG_SYNC = "open_and_process_table signal truncate_before_lock wait_for forever";
--send TRUNCATE t1
connect(con1,localhost,root,,);
SET DEBUG_SYNC = "now wait_for truncate_before_lock";
# Wait for one connection to reach open_and_process_table.
--let $show_statement= SHOW PROCESSLIST
--let $field= State
--let $condition= 'debug sync point: open_and_process_table';
--source include/wait_show_condition.inc
SELECT ((@id := id) - id) FROM information_schema.processlist WHERE processlist.info LIKE '%TRUNCATE t1%' AND state LIKE '%open_and_process_table%';
# Test killing from mysql server
KILL QUERY @id;
connection default;
--ERROR ER_QUERY_INTERRUPTED
--reap
connection con1;
--source include/show_binlog_events.inc
disconnect con1;
--source include/wait_until_disconnected.inc
connection default;
SELECT * FROM t1;
DROP TABLE t1;
SET DEBUG_SYNC= 'RESET';
--disable_connect_log
/* Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
/* Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -181,12 +181,19 @@ fk_truncate_illegal_if_parent(THD *thd, TABLE *table)
@param table_ref Table list element for the table to be truncated.
@param is_tmp_table True if element refers to a temp table.
@retval 0 Success.
@retval > 0 Error code.
@retval TRUNCATE_OK Truncate was successful and statement can be safely
binlogged.
@retval TRUNCATE_FAILED_BUT_BINLOG Truncate failed but still go ahead with
binlogging as in case of non transactional tables
partial truncation is possible.
@retval TRUNCATE_FAILED_SKIP_BINLOG Truncate was not successful hence donot
binlong the statement.
*/
int Truncate_statement::handler_truncate(THD *thd, TABLE_LIST *table_ref,
bool is_tmp_table)
enum Truncate_statement::truncate_result
Truncate_statement::handler_truncate(THD *thd, TABLE_LIST *table_ref,
bool is_tmp_table)
{
int error= 0;
uint flags;
......@@ -228,16 +235,30 @@ int Truncate_statement::handler_truncate(THD *thd, TABLE_LIST *table_ref,
/* Open the table as it will handle some required preparations. */
if (open_and_lock_tables(thd, table_ref, FALSE, flags))
DBUG_RETURN(1);
DBUG_RETURN(TRUNCATE_FAILED_SKIP_BINLOG);
/* Whether to truncate regardless of foreign keys. */
if (! (thd->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS))
error= fk_truncate_illegal_if_parent(thd, table_ref->table);
if (fk_truncate_illegal_if_parent(thd, table_ref->table))
DBUG_RETURN(TRUNCATE_FAILED_SKIP_BINLOG);
if (!error && (error= table_ref->table->file->ha_truncate()))
error= table_ref->table->file->ha_truncate();
if (error)
{
table_ref->table->file->print_error(error, MYF(0));
DBUG_RETURN(error);
/*
If truncate method is not implemented then we don't binlog the
statement. If truncation has failed in a transactional engine then also we
donot binlog the statment. Only in non transactional engine we binlog
inspite of errors.
*/
if (error == HA_ERR_WRONG_COMMAND ||
table_ref->table->file->has_transactions())
DBUG_RETURN(TRUNCATE_FAILED_SKIP_BINLOG);
else
DBUG_RETURN(TRUNCATE_FAILED_BUT_BINLOG);
}
DBUG_RETURN(TRUNCATE_OK);
}
......@@ -470,10 +491,14 @@ bool Truncate_statement::truncate_table(THD *thd, TABLE_LIST *table_ref)
/*
All effects of a TRUNCATE TABLE operation are committed even if
truncation fails. Thus, the query must be written to the binary
log. The only exception is a unimplemented truncate method.
truncation fails in the case of non transactional tables. Thus, the
query must be written to the binary log. The only exception is a
unimplemented truncate method.
*/
binlog_stmt= !error || error != HA_ERR_WRONG_COMMAND;
if (error == TRUNCATE_OK || error == TRUNCATE_FAILED_BUT_BINLOG)
binlog_stmt= true;
else
binlog_stmt= false;
}
/*
......
#ifndef SQL_TRUNCATE_INCLUDED
#define SQL_TRUNCATE_INCLUDED
/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
/* Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -47,11 +47,17 @@ class Truncate_statement : public Sql_statement
bool execute(THD *thd);
protected:
enum truncate_result{
TRUNCATE_OK=0,
TRUNCATE_FAILED_BUT_BINLOG,
TRUNCATE_FAILED_SKIP_BINLOG
};
/** Handle locking a base table for truncate. */
bool lock_table(THD *, TABLE_LIST *, bool *);
/** Truncate table via the handler method. */
int handler_truncate(THD *, TABLE_LIST *, bool);
enum truncate_result handler_truncate(THD *, TABLE_LIST *, bool);
/**
Optimized delete of all rows by doing a full regenerate of the table.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment