summaryrefslogtreecommitdiff
path: root/sql/sql_insert.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/sql_insert.cc')
-rw-r--r--sql/sql_insert.cc278
1 files changed, 150 insertions, 128 deletions
diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc
index 99a8378961e..d807ac36278 100644
--- a/sql/sql_insert.cc
+++ b/sql/sql_insert.cc
@@ -1,4 +1,4 @@
-/* Copyright 2000-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc.
+/* Copyright (c) 2000, 2011, 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
@@ -11,8 +11,7 @@
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
-
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
/* Insert of records */
@@ -77,7 +76,8 @@
#include "sql_audit.h"
#ifndef EMBEDDED_LIBRARY
-static bool delayed_get_table(THD *thd, TABLE_LIST *table_list);
+static bool delayed_get_table(THD *thd, MDL_request *grl_protection_request,
+ TABLE_LIST *table_list);
static int write_delayed(THD *thd, TABLE *table, enum_duplicates duplic,
LEX_STRING query, bool ignore, bool log_on);
static void end_delayed_insert(THD *thd);
@@ -415,9 +415,9 @@ void prepare_triggers_for_insert_stmt(TABLE *table)
downgrade the lock in handler::store_lock() method.
*/
-void upgrade_lock_type_for_insert(THD *thd, thr_lock_type *lock_type,
- enum_duplicates duplic,
- bool is_multi_insert)
+static
+void upgrade_lock_type(THD *thd, thr_lock_type *lock_type,
+ enum_duplicates duplic)
{
if (duplic == DUP_UPDATE ||
(duplic == DUP_REPLACE && *lock_type == TL_WRITE_CONCURRENT_INSERT))
@@ -466,10 +466,9 @@ void upgrade_lock_type_for_insert(THD *thd, thr_lock_type *lock_type,
return;
}
- bool log_on= (thd->variables.option_bits & OPTION_BIN_LOG ||
- ! (thd->security_ctx->master_access & SUPER_ACL));
+ bool log_on= (thd->variables.option_bits & OPTION_BIN_LOG);
if (global_system_variables.binlog_format == BINLOG_FORMAT_STMT &&
- log_on && mysql_bin_log.is_open() && is_multi_insert)
+ log_on && mysql_bin_log.is_open())
{
/*
Statement-based binary logging does not work in this case, because:
@@ -526,32 +525,28 @@ void upgrade_lock_type_for_insert(THD *thd, thr_lock_type *lock_type,
static
bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list)
{
+ MDL_request protection_request;
DBUG_ENTER("open_and_lock_for_insert_delayed");
#ifndef EMBEDDED_LIBRARY
- if (thd->locked_tables_mode && thd->global_read_lock.is_acquired())
- {
- /*
- If this connection has the global read lock, the handler thread
- will not be able to lock the table. It will wait for the global
- read lock to go away, but this will never happen since the
- connection thread will be stuck waiting for the handler thread
- to open and lock the table.
- If we are not in locked tables mode, INSERT will seek protection
- against the global read lock (and fail), thus we will only get
- to this point in locked tables mode.
- */
- my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0));
- DBUG_RETURN(TRUE);
- }
-
/*
In order for the deadlock detector to be able to find any deadlocks
- caused by the handler thread locking this table, we take the metadata
- lock inside the connection thread. If this goes ok, the ticket is cloned
- and added to the list of granted locks held by the handler thread.
+ caused by the handler thread waiting for GRL or this table, we acquire
+ protection against GRL (global IX metadata lock) and metadata lock on
+ table to being inserted into inside the connection thread.
+ If this goes ok, the tickets are cloned and added to the list of granted
+ locks held by the handler thread.
*/
- MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint();
+ if (thd->global_read_lock.can_acquire_protection())
+ DBUG_RETURN(TRUE);
+
+ protection_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
+ MDL_STATEMENT);
+
+ if (thd->mdl_context.acquire_lock(&protection_request,
+ thd->variables.lock_wait_timeout))
+ DBUG_RETURN(TRUE);
+
if (thd->mdl_context.acquire_lock(&table_list->mdl_request,
thd->variables.lock_wait_timeout))
/*
@@ -561,7 +556,7 @@ bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list)
DBUG_RETURN(TRUE);
bool error= FALSE;
- if (delayed_get_table(thd, table_list))
+ if (delayed_get_table(thd, &protection_request, table_list))
error= TRUE;
else if (table_list->table)
{
@@ -586,13 +581,13 @@ bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list)
}
/*
- If a lock was acquired above, we should release it after
- handle_delayed_insert() has cloned the ticket. Note that acquire_lock() can
- succeed because the connection already has the lock. In this case the ticket
- will be before the mdl_savepoint and we should not release it here.
+ We can't release protection against GRL and metadata lock on the table
+ being inserted into here. These locks might be required, for example,
+ because this INSERT DELAYED calls functions which may try to update
+ this or another tables (updating the same table is of course illegal,
+ but such an attempt can be discovered only later during statement
+ execution).
*/
- if (!thd->mdl_context.has_lock(mdl_savepoint, table_list->mdl_request.ticket))
- thd->mdl_context.release_lock(table_list->mdl_request.ticket);
/*
Reset the ticket in case we end up having to use normal insert and
@@ -631,14 +626,12 @@ bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list)
static int
create_insert_stmt_from_insert_delayed(THD *thd, String *buf)
{
- /* Append the part of thd->query before "DELAYED" keyword */
- if (buf->append(thd->query(),
- thd->lex->keyword_delayed_begin - thd->query()))
- return 1;
- /* Append the part of thd->query after "DELAYED" keyword */
- if (buf->append(thd->lex->keyword_delayed_begin + 7))
+ /* Make a copy of thd->query() and then remove the "DELAYED" keyword */
+ if (buf->append(thd->query()) ||
+ buf->replace(thd->lex->keyword_delayed_begin_offset,
+ thd->lex->keyword_delayed_end_offset -
+ thd->lex->keyword_delayed_begin_offset, 0))
return 1;
-
return 0;
}
@@ -680,8 +673,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
By default, both logs are enabled (this won't cause problems if the server
runs without --log-bin).
*/
- bool log_on= ((thd->variables.option_bits & OPTION_BIN_LOG) ||
- (!(thd->security_ctx->master_access & SUPER_ACL)));
+ bool log_on= (thd->variables.option_bits & OPTION_BIN_LOG);
#endif
thr_lock_type lock_type;
Item *unused_conds= 0;
@@ -691,8 +683,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
Upgrade lock type if the requested lock is incompatible with
the current connection mode or table operation.
*/
- upgrade_lock_type_for_insert(thd, &table_list->lock_type, duplic,
- values_list.elements > 1);
+ upgrade_lock_type(thd, &table_list->lock_type, duplic);
/*
We can't write-delayed into a table locked with LOCK TABLES:
@@ -989,7 +980,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
if (thd->transaction.stmt.modified_non_trans_table)
thd->transaction.all.modified_non_trans_table= TRUE;
- if ((changed && error <= 0) ||
+ if (error <= 0 ||
thd->transaction.stmt.modified_non_trans_table ||
was_insert_delayed)
{
@@ -1028,7 +1019,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
DBUG_ASSERT(thd->killed != THD::KILL_BAD_DATA || error > 0);
if (was_insert_delayed && table_list->lock_type == TL_WRITE)
{
- /* Binlog multi INSERT DELAYED as INSERT without DELAYED. */
+ /* Binlog INSERT DELAYED as INSERT without DELAYED. */
String log_query;
if (create_insert_stmt_from_insert_delayed(thd, &log_query))
{
@@ -1635,9 +1626,7 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
table->file->adjust_next_insert_id_after_explicit_value(
table->next_number_field->val_int());
info->touched++;
- if (((table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ) &&
- !bitmap_is_subset(table->write_set, table->read_set)) ||
- compare_record(table))
+ if (!records_are_comparable(table) || compare_records(table))
{
if ((error=table->file->ha_update_row(table->record[1],
table->record[0])) &&
@@ -1890,10 +1879,11 @@ public:
mysql_cond_t cond, cond_client;
volatile uint tables_in_use,stacked_inserts;
volatile bool status;
- /*
+ /**
When the handler thread starts, it clones a metadata lock ticket
- for the table to be inserted. This is done to allow the deadlock
- detector to detect deadlocks resulting from this lock.
+ which protects against GRL and ticket for the table to be inserted.
+ This is done to allow the deadlock detector to detect deadlocks
+ resulting from these locks.
Before this is done, the connection thread cannot safely exit
without causing problems for clone_ticket().
Once handler_thread_initialized has been set, it is safe for the
@@ -1905,6 +1895,11 @@ public:
I_List<delayed_row> rows;
ulong group_count;
TABLE_LIST table_list; // Argument
+ /**
+ Request for IX metadata lock protecting against GRL which is
+ passed from connection thread to the handler thread.
+ */
+ MDL_request grl_protection;
Delayed_insert()
:locks_in_memory(0), table(0),tables_in_use(0),stacked_inserts(0),
@@ -1920,22 +1915,6 @@ public:
thd.lex->current_select= 0; // for my_message_sql
thd.lex->sql_command= SQLCOM_INSERT; // For innodb::store_lock()
/*
- Statement-based replication of INSERT DELAYED has problems with
- RAND() and user variables, so in mixed mode we go to row-based.
- For normal commands, the unsafe flag is set at parse time.
- However, since the flag is a member of the THD object, of which
- the delayed_insert thread has its own copy, we must set the
- statement to unsafe here and explicitly set row logging mode.
-
- @todo set_current_stmt_binlog_format_row_if_mixed should not be
- called by anything else than thd->decide_logging_format(). When
- we call set_current_blah here, none of the checks in
- decide_logging_format is made. We should probably call
- thd->decide_logging_format() directly instead. /Sven
- */
- thd.lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_INSERT_DELAYED);
- thd.set_current_stmt_binlog_format_row_if_mixed();
- /*
Prevent changes to global.lock_wait_timeout from affecting
delayed insert threads as any timeouts in delayed inserts
are not communicated to the client.
@@ -2083,7 +2062,8 @@ Delayed_insert *find_handler(THD *thd, TABLE_LIST *table_list)
*/
static
-bool delayed_get_table(THD *thd, TABLE_LIST *table_list)
+bool delayed_get_table(THD *thd, MDL_request *grl_protection_request,
+ TABLE_LIST *table_list)
{
int error;
Delayed_insert *di;
@@ -2116,7 +2096,8 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list)
mysql_mutex_unlock(&LOCK_thread_count);
di->thd.set_db(table_list->db, (uint) strlen(table_list->db));
di->thd.set_query(my_strdup(table_list->table_name,
- MYF(MY_WME | ME_FATALERROR)), 0);
+ MYF(MY_WME | ME_FATALERROR)),
+ 0, system_charset_info);
if (di->thd.db == NULL || di->thd.query() == NULL)
{
/* The error is reported */
@@ -2127,7 +2108,10 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list)
/* Replace volatile strings with local copies */
di->table_list.alias= di->table_list.table_name= di->thd.query();
di->table_list.db= di->thd.db;
- /* We need the ticket so that it can be cloned in handle_delayed_insert */
+ /* We need the tickets so that they can be cloned in handle_delayed_insert */
+ di->grl_protection.init(MDL_key::GLOBAL, "", "",
+ MDL_INTENTION_EXCLUSIVE, MDL_STATEMENT);
+ di->grl_protection.ticket= grl_protection_request->ticket;
init_mdl_requests(&di->table_list);
di->table_list.mdl_request.ticket= table_list->mdl_request.ticket;
@@ -2516,6 +2500,65 @@ void kill_delayed_threads(void)
/**
+ A strategy for the prelocking algorithm which prevents the
+ delayed insert thread from opening tables with engines which
+ do not support delayed inserts.
+
+ Particularly it allows to abort open_tables() as soon as we
+ discover that we have opened a MERGE table, without acquiring
+ metadata locks on underlying tables.
+*/
+
+class Delayed_prelocking_strategy : public Prelocking_strategy
+{
+public:
+ virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
+ Sroutine_hash_entry *rt, sp_head *sp,
+ bool *need_prelocking);
+ virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking);
+ virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking);
+};
+
+
+bool Delayed_prelocking_strategy::
+handle_table(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking)
+{
+ DBUG_ASSERT(table_list->lock_type == TL_WRITE_DELAYED);
+
+ if (!(table_list->table->file->ha_table_flags() & HA_CAN_INSERT_DELAYED))
+ {
+ my_error(ER_DELAYED_NOT_SUPPORTED, MYF(0), table_list->table_name);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+bool Delayed_prelocking_strategy::
+handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
+ Sroutine_hash_entry *rt, sp_head *sp,
+ bool *need_prelocking)
+{
+ /* LEX used by the delayed insert thread has no routines. */
+ DBUG_ASSERT(0);
+ return FALSE;
+}
+
+
+bool Delayed_prelocking_strategy::
+handle_view(THD *thd, Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list, bool *need_prelocking)
+{
+ /* We don't open views in the delayed insert thread. */
+ DBUG_ASSERT(0);
+ return FALSE;
+}
+
+
+/**
Open and lock table for use by delayed thread and check that
this table is suitable for delayed inserts.
@@ -2525,21 +2568,21 @@ void kill_delayed_threads(void)
bool Delayed_insert::open_and_lock_table()
{
+ Delayed_prelocking_strategy prelocking_strategy;
+
+ /*
+ Use special prelocking strategy to get ER_DELAYED_NOT_SUPPORTED
+ error for tables with engines which don't support delayed inserts.
+ */
if (!(table= open_n_lock_single_table(&thd, &table_list,
TL_WRITE_DELAYED,
- MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK)))
+ MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK,
+ &prelocking_strategy)))
{
thd.fatal_error(); // Abort waiting inserts
return TRUE;
}
- if (!(table->file->ha_table_flags() & HA_CAN_INSERT_DELAYED))
- {
- /* To rollback InnoDB statement transaction. */
- trans_rollback_stmt(&thd);
- my_error(ER_DELAYED_NOT_SUPPORTED, MYF(ME_FATALERROR),
- table_list.table_name);
- return TRUE;
- }
+
if (table->triggers)
{
/*
@@ -2605,21 +2648,23 @@ pthread_handler_t handle_delayed_insert(void *arg)
}
thd->lex->sql_command= SQLCOM_INSERT; // For innodb::store_lock()
+
/*
- Statement-based replication of INSERT DELAYED has problems with RAND()
- and user vars, so in mixed mode we go to row-based.
+ INSERT DELAYED has to go to row-based format because the time
+ at which rows are inserted cannot be determined in mixed mode.
*/
- thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_INSERT_DELAYED);
thd->set_current_stmt_binlog_format_row_if_mixed();
/*
- Clone the ticket representing the lock on the target table for
- the insert and add it to the list of granted metadata locks held by
- the handler thread. This is safe since the handler thread is
- not holding nor waiting on any metadata locks.
+ Clone tickets representing protection against GRL and the lock on
+ the target table for the insert and add them to the list of granted
+ metadata locks held by the handler thread. This is safe since the
+ handler thread is not holding nor waiting on any metadata locks.
*/
- if (thd->mdl_context.clone_ticket(&di->table_list.mdl_request))
+ if (thd->mdl_context.clone_ticket(&di->grl_protection) ||
+ thd->mdl_context.clone_ticket(&di->table_list.mdl_request))
{
+ thd->mdl_context.release_transactional_locks();
di->handler_thread_initialized= TRUE;
goto err;
}
@@ -2875,6 +2920,13 @@ bool Delayed_insert::handle_inserts(void)
if (log_query)
{
/*
+ Guaranteed that the INSERT DELAYED STMT will not be here
+ in SBR when mysql binlog is enabled.
+ */
+ DBUG_ASSERT(!(mysql_bin_log.is_open() &&
+ !thd.is_current_stmt_binlog_format_row()));
+
+ /*
This is the first value of an INSERT statement.
It is the right place to clear a forced insert_id.
This is usually done after the last value of an INSERT statement,
@@ -2941,39 +2993,6 @@ bool Delayed_insert::handle_inserts(void)
table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
}
- if (log_query && mysql_bin_log.is_open())
- {
- bool backup_time_zone_used = thd.time_zone_used;
- Time_zone *backup_time_zone = thd.variables.time_zone;
- if (row->time_zone != NULL)
- {
- thd.time_zone_used = true;
- thd.variables.time_zone = row->time_zone;
- }
-
- /* if the delayed insert was killed, the killed status is
- ignored while binlogging */
- int errcode= 0;
- if (thd.killed == THD::NOT_KILLED)
- errcode= query_error_code(&thd, TRUE);
-
- /*
- If the query has several rows to insert, only the first row will come
- here. In row-based binlogging, this means that the first row will be
- written to binlog as one Table_map event and one Rows event (due to an
- event flush done in binlog_query()), then all other rows of this query
- will be binlogged together as one single Table_map event and one
- single Rows event.
- */
- if (thd.binlog_query(THD::ROW_QUERY_TYPE,
- row->query.str, row->query.length,
- FALSE, FALSE, FALSE, errcode))
- goto err;
-
- thd.time_zone_used = backup_time_zone_used;
- thd.variables.time_zone = backup_time_zone;
- }
-
if (table->s->blob_fields)
free_delayed_insert_blobs(table);
thread_safe_decrement(delayed_rows_in_use,&LOCK_delayed_status);
@@ -3470,6 +3489,9 @@ bool select_insert::send_eof()
error= (thd->locked_tables_mode <= LTM_LOCK_TABLES ?
table->file->ha_end_bulk_insert() : 0);
+ if (!error && thd->is_error())
+ error= thd->stmt_da->sql_errno();
+
table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
@@ -3895,7 +3917,7 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
if (table->s->fields < values.elements)
{
- my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), 1);
+ my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), 1L);
DBUG_RETURN(-1);
}
@@ -4022,7 +4044,7 @@ bool select_create::send_eof()
{
bool tmp=select_insert::send_eof();
if (tmp)
- abort();
+ abort_result_set();
else
{
/*
@@ -4054,7 +4076,7 @@ void select_create::abort_result_set()
DBUG_ENTER("select_create::abort_result_set");
/*
- In select_insert::abort() we roll back the statement, including
+ In select_insert::abort_result_set() we roll back the statement, including
truncating the transaction cache of the binary log. To do this, we
pretend that the statement is transactional, even though it might
be the case that it was not.