Commit 157c42de authored by unknown's avatar unknown
Browse files

Fix Bug #18559 "log tables cannot change engine, and

                gets deadlocked when dropping w/ log on"

Log tables rely on concurrent insert machinery to add data.
This means that log tables are always opened and locked by
special (artificial) logger threads. Because of this, the thread
which tries to drop a log table starts to wait for the table
to be unlocked. Which will happen only if the log table is disabled.
Alike situation happens if one tries to alter a log table.
However in addition to the problem above, alter table calls
check_if_locking_is_allowed() routine for the engine. The
routine does not allow alter for the log tables. So, alter
doesn't start waiting forever for logs to be disabled, but 
returns with an error.
Another problem is that not all engines could be used for
the log tables. That's because they need concurrent insert.

In this patch we:
(1) Explicitly disallow to drop/alter a log table if it
    is currently used by the logger.
(2) Update MyISAM to support log tables
(3) Allow to drop log tables/alter log tables if log is
    disabled
At the same time we (4) Disallow to alter log tables to
unsupported engine (after this patch CSV and MyISAM are 
alowed)
Recommit with review fixes.


mysql-test/r/log_tables.result:
  Update result file.
  Note: there are warnings in result file. This is because of CSV
  bug (Bug #21328). They should go away after it is fixed.
mysql-test/t/log_tables.test:
  Add a test for the bug
sql/ha_myisam.cc:
  Add log table handling to myisam: as log tables
  use concurrent insert, they are typically
  locked with TL_CONCURRERENT_INSERT lock. So,
  disallow other threads to attempt locking of
  the log tables in incompatible modes. Because
  otherwise the threads will wait for the tables
  to be unlocked forever.
sql/handler.cc:
  Add a function to check if a table we're going to lock
  is a log table and if the lock mode we want allowed
sql/handler.h:
  Add a new function to check compatibility of the locking
sql/log.cc:
  we shouldn't close the log table if and only
  if this particular table is already closed
sql/log.h:
  add new functions to check if a log is enabled
sql/share/errmsg.txt:
  add new error messages
sql/sql_table.cc:
  DROP and ALTER TABLE should not work on log
  tables if the log tables are enabled
storage/csv/ha_tina.cc:
  move function to check if the locking for the log
  tables allowed to handler class, so that we can
  reuse it in other engines.
storage/myisam/mi_extra.c:
  add new ::extra() flag processing to myisam
storage/myisam/mi_open.c:
  init log table flag
storage/myisam/mi_write.c:
  update status after each write if it's a log table
storage/myisam/myisamdef.h:
  Add new log table flag to myisam share.
  We need it to distinguish between usual
  and log tables, as for the log tables we
  should provide concurrent insert in a
  different way than for usual tables: we
  want new rows to be immediately visible
  to other threads.
parent d090462a
Loading
Loading
Loading
Loading
+141 −0
Original line number Diff line number Diff line
@@ -72,3 +72,144 @@ sleep(2)
select * from mysql.slow_log;
start_time	user_host	query_time	lock_time	rows_sent	rows_examined	db	last_insert_id	insert_id	server_id	sql_text
TIMESTAMP	USER_HOST	QUERY_TIME	00:00:00	1	0	test	0	0	1	select sleep(2)
alter table mysql.general_log engine=myisam;
ERROR HY000: You can't alter a log table if logging is enabled
alter table mysql.slow_log engine=myisam;
ERROR HY000: You can't alter a log table if logging is enabled
drop table mysql.general_log;
ERROR HY000: Cannot drop log table if log is enabled
drop table mysql.slow_log;
ERROR HY000: Cannot drop log table if log is enabled
set global general_log='OFF';
alter table mysql.slow_log engine=myisam;
ERROR HY000: You can't alter a log table if logging is enabled
set global slow_query_log='OFF';
show create table mysql.general_log;
Table	Create Table
general_log	CREATE TABLE `general_log` (
  `event_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `user_host` mediumtext,
  `thread_id` int(11) DEFAULT NULL,
  `server_id` int(11) DEFAULT NULL,
  `command_type` varchar(64) DEFAULT NULL,
  `argument` mediumtext
) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT='General log'
show create table mysql.slow_log;
Table	Create Table
slow_log	CREATE TABLE `slow_log` (
  `start_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `user_host` mediumtext NOT NULL,
  `query_time` time NOT NULL,
  `lock_time` time NOT NULL,
  `rows_sent` int(11) NOT NULL,
  `rows_examined` int(11) NOT NULL,
  `db` varchar(512) DEFAULT NULL,
  `last_insert_id` int(11) DEFAULT NULL,
  `insert_id` int(11) DEFAULT NULL,
  `server_id` int(11) DEFAULT NULL,
  `sql_text` mediumtext NOT NULL
) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT='Slow log'
alter table mysql.general_log engine=myisam;
alter table mysql.slow_log engine=myisam;
Warnings:
Warning	1264	Out of range value for column 'last_insert_id' at row 0
Warning	1264	Out of range value for column 'insert_id' at row 0
show create table mysql.general_log;
Table	Create Table
general_log	CREATE TABLE `general_log` (
  `event_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `user_host` mediumtext,
  `thread_id` int(11) DEFAULT NULL,
  `server_id` int(11) DEFAULT NULL,
  `command_type` varchar(64) DEFAULT NULL,
  `argument` mediumtext
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='General log'
show create table mysql.slow_log;
Table	Create Table
slow_log	CREATE TABLE `slow_log` (
  `start_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `user_host` mediumtext NOT NULL,
  `query_time` time NOT NULL,
  `lock_time` time NOT NULL,
  `rows_sent` int(11) NOT NULL,
  `rows_examined` int(11) NOT NULL,
  `db` varchar(512) DEFAULT NULL,
  `last_insert_id` int(11) DEFAULT NULL,
  `insert_id` int(11) DEFAULT NULL,
  `server_id` int(11) DEFAULT NULL,
  `sql_text` mediumtext NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Slow log'
set global general_log='ON';
set global slow_query_log='ON';
select * from mysql.general_log;
event_time	user_host	thread_id	server_id	command_type	argument
TIMESTAMP	USER_HOST	THREAD_ID	1	Query	set names utf8
TIMESTAMP	USER_HOST	THREAD_ID	1	Query	create table bug16905 (s char(15) character set utf8 default 'пусто')
TIMESTAMP	USER_HOST	THREAD_ID	1	Query	insert into bug16905 values ('новое')
TIMESTAMP	USER_HOST	THREAD_ID	1	Query	select * from mysql.general_log
TIMESTAMP	USER_HOST	THREAD_ID	1	Query	drop table bug16905
TIMESTAMP	USER_HOST	THREAD_ID	1	Query	truncate table mysql.slow_log
TIMESTAMP	USER_HOST	THREAD_ID	1	Query	set session long_query_time=1
TIMESTAMP	USER_HOST	THREAD_ID	1	Query	select sleep(2)
TIMESTAMP	USER_HOST	THREAD_ID	1	Query	select * from mysql.slow_log
TIMESTAMP	USER_HOST	THREAD_ID	1	Query	alter table mysql.general_log engine=myisam
TIMESTAMP	USER_HOST	THREAD_ID	1	Query	alter table mysql.slow_log engine=myisam
TIMESTAMP	USER_HOST	THREAD_ID	1	Query	drop table mysql.general_log
TIMESTAMP	USER_HOST	THREAD_ID	1	Query	drop table mysql.slow_log
TIMESTAMP	USER_HOST	THREAD_ID	1	Query	set global general_log='OFF'
TIMESTAMP	USER_HOST	THREAD_ID	1	Query	set global slow_query_log='ON'
TIMESTAMP	USER_HOST	THREAD_ID	1	Query	select * from mysql.general_log
flush logs;
lock tables mysql.general_log WRITE;
ERROR HY000: You can't write-lock a log table. Only read access is possible.
lock tables mysql.slow_log WRITE;
ERROR HY000: You can't write-lock a log table. Only read access is possible.
lock tables mysql.general_log READ;
ERROR HY000: You can't use usual read lock with log tables. Try READ LOCAL instead.
lock tables mysql.slow_log READ;
ERROR HY000: You can't use usual read lock with log tables. Try READ LOCAL instead.
lock tables mysql.slow_log READ LOCAL, mysql.general_log READ LOCAL;
unlock tables;
set global general_log='OFF';
set global slow_query_log='OFF';
alter table mysql.slow_log engine=ndb;
ERROR HY000: One can use only CSV and MyISAM engines for the log tables
alter table mysql.slow_log engine=innodb;
ERROR HY000: One can use only CSV and MyISAM engines for the log tables
alter table mysql.slow_log engine=archive;
ERROR HY000: One can use only CSV and MyISAM engines for the log tables
alter table mysql.slow_log engine=blackhole;
ERROR HY000: One can use only CSV and MyISAM engines for the log tables
drop table mysql.slow_log;
drop table mysql.general_log;
drop table mysql.general_log;
ERROR 42S02: Unknown table 'general_log'
drop table mysql.slow_log;
ERROR 42S02: Unknown table 'slow_log'
use mysql;
CREATE TABLE `general_log` (
`event_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP,
`user_host` mediumtext,
`thread_id` int(11) DEFAULT NULL,
`server_id` int(11) DEFAULT NULL,
`command_type` varchar(64) DEFAULT NULL,
`argument` mediumtext
) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT='General log';
CREATE TABLE `slow_log` (
`start_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP,
`user_host` mediumtext NOT NULL,
`query_time` time NOT NULL,
`lock_time` time NOT NULL,
`rows_sent` int(11) NOT NULL,
`rows_examined` int(11) NOT NULL,
`db` varchar(512) DEFAULT NULL,
`last_insert_id` int(11) DEFAULT NULL,
`insert_id` int(11) DEFAULT NULL,
`server_id` int(11) DEFAULT NULL,
`sql_text` mediumtext NOT NULL
) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT='Slow log';
set global general_log='ON';
set global slow_query_log='ON';
use test;
+133 −0
Original line number Diff line number Diff line
@@ -171,6 +171,139 @@ select sleep(2);
--replace_column 1 TIMESTAMP 2 USER_HOST 3 QUERY_TIME
select * from mysql.slow_log;

#
# Bug #18559 log tables cannot change engine, and gets deadlocked when
# dropping w/ log on
#

# check that appropriate error messages are given when one attempts to alter
# or drop a log tables, while corresponding logs are enabled
--error ER_CANT_ALTER_LOG_TABLE
alter table mysql.general_log engine=myisam;
--error ER_CANT_ALTER_LOG_TABLE
alter table mysql.slow_log engine=myisam;

--error ER_CANT_DROP_LOG_TABLE
drop table mysql.general_log;
--error ER_CANT_DROP_LOG_TABLE
drop table mysql.slow_log;

# check that one can alter log tables to MyISAM
set global general_log='OFF';

# cannot convert another log table
--error ER_CANT_ALTER_LOG_TABLE
alter table mysql.slow_log engine=myisam;

# alter both tables
set global slow_query_log='OFF';
# check that both tables use CSV engine
show create table mysql.general_log;
show create table mysql.slow_log;

alter table mysql.general_log engine=myisam;
alter table mysql.slow_log engine=myisam;

# check that the tables were converted
show create table mysql.general_log;
show create table mysql.slow_log;

# enable log tables and chek that new tables indeed work
set global general_log='ON';
set global slow_query_log='ON';

--replace_column 1 TIMESTAMP 2 USER_HOST 3 THREAD_ID
select * from mysql.general_log;

# check that flush of myisam-based log tables work fine
flush logs;

# check locking of myisam-based log tables

--error ER_CANT_WRITE_LOCK_LOG_TABLE
lock tables mysql.general_log WRITE;

--error ER_CANT_WRITE_LOCK_LOG_TABLE
lock tables mysql.slow_log WRITE;

#
# This attemts to get TL_READ_NO_INSERT lock, which is incompatible with
# TL_WRITE_CONCURRENT_INSERT. This should fail. We issue this error as log
# tables are always opened and locked by the logger.
#

--error ER_CANT_READ_LOCK_LOG_TABLE
lock tables mysql.general_log READ;

--error ER_CANT_READ_LOCK_LOG_TABLE
lock tables mysql.slow_log READ;

#
# This call should result in TL_READ lock on the log table. This is ok and
# should pass.
#

lock tables mysql.slow_log READ LOCAL, mysql.general_log READ LOCAL;

unlock tables;

# check that we can drop them
set global general_log='OFF';
set global slow_query_log='OFF';

# check that alter table doesn't work for other engines
--error ER_BAD_LOG_ENGINE
alter table mysql.slow_log engine=ndb;
--error ER_BAD_LOG_ENGINE
alter table mysql.slow_log engine=innodb;
--error ER_BAD_LOG_ENGINE
alter table mysql.slow_log engine=archive;
--error ER_BAD_LOG_ENGINE
alter table mysql.slow_log engine=blackhole;

drop table mysql.slow_log;
drop table mysql.general_log;

# check that table share cleanup is performed correctly (double drop)

--error ER_BAD_TABLE_ERROR
drop table mysql.general_log;
--error ER_BAD_TABLE_ERROR
drop table mysql.slow_log;

# recreate tables and enable logs

use mysql;

CREATE TABLE `general_log` (
  `event_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
                         ON UPDATE CURRENT_TIMESTAMP,
  `user_host` mediumtext,
  `thread_id` int(11) DEFAULT NULL,
  `server_id` int(11) DEFAULT NULL,
  `command_type` varchar(64) DEFAULT NULL,
  `argument` mediumtext
) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT='General log';

CREATE TABLE `slow_log` (
  `start_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
                         ON UPDATE CURRENT_TIMESTAMP,
  `user_host` mediumtext NOT NULL,
  `query_time` time NOT NULL,
  `lock_time` time NOT NULL,
  `rows_sent` int(11) NOT NULL,
  `rows_examined` int(11) NOT NULL,
  `db` varchar(512) DEFAULT NULL,
  `last_insert_id` int(11) DEFAULT NULL,
  `insert_id` int(11) DEFAULT NULL,
  `server_id` int(11) DEFAULT NULL,
  `sql_text` mediumtext NOT NULL
) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT='Slow log';

set global general_log='ON';
set global slow_query_log='ON';
use test;

# kill all connections
disconnect con1;
disconnect con2;
+9 −0
Original line number Diff line number Diff line
@@ -274,6 +274,15 @@ bool ha_myisam::check_if_locking_is_allowed(uint sql_command,
             table->s->table_name.str);
    return FALSE;
  }

  /*
    Deny locking of the log tables, which is incompatible with
    concurrent insert. Unless called from a logger THD:
    general_log_thd or slow_log_thd.
  */
  if (!called_by_logger_thread)
    return check_if_log_table_locking_is_allowed(sql_command, type, table);

  return TRUE;
}

+28 −0
Original line number Diff line number Diff line
@@ -1422,6 +1422,34 @@ void handler::ha_statistic_increment(ulong SSV::*offset) const
  statistic_increment(table->in_use->status_var.*offset, &LOCK_status);
}


bool handler::check_if_log_table_locking_is_allowed(uint sql_command,
                                                    ulong type, TABLE *table)
{
  /*
    Deny locking of the log tables, which is incompatible with
    concurrent insert. Unless called from a logger THD:
    general_log_thd or slow_log_thd.
  */
  if (table->s->log_table &&
      sql_command != SQLCOM_TRUNCATE &&
      sql_command != SQLCOM_ALTER_TABLE &&
      !(sql_command == SQLCOM_FLUSH &&
        type & REFRESH_LOG) &&
      (table->reginfo.lock_type >= TL_READ_NO_INSERT))
  {
    /*
      The check  >= TL_READ_NO_INSERT denies all write locks
      plus the only read lock (TL_READ_NO_INSERT itself)
    */
    table->reginfo.lock_type == TL_READ_NO_INSERT ?
      my_error(ER_CANT_READ_LOCK_LOG_TABLE, MYF(0)) :
        my_error(ER_CANT_WRITE_LOCK_LOG_TABLE, MYF(0));
    return FALSE;
  }
  return TRUE;
}

/*
  Open database-handler.

+2 −0
Original line number Diff line number Diff line
@@ -974,6 +974,8 @@ class handler :public Sql_alloc
  {
    return TRUE;
  }
  bool check_if_log_table_locking_is_allowed(uint sql_command,
                                             ulong type, TABLE *table);
  int ha_open(TABLE *table, const char *name, int mode, int test_if_locked);
  void adjust_next_insert_id_after_explicit_value(ulonglong nr);
  bool update_auto_increment();
Loading