diff options
author | Michael Widenius <monty@askmonty.org> | 2012-08-07 07:25:15 +0300 |
---|---|---|
committer | Michael Widenius <monty@askmonty.org> | 2012-08-07 07:25:15 +0300 |
commit | b39e6e3d093b45f792959ef06fea1c175263ae1a (patch) | |
tree | b642aba6ff438e50d67a884f4cb5d9c6c3287644 /sql | |
parent | 0a70eb33d159036c5d3f6929df57c2f18bda8471 (diff) | |
download | mariadb-git-b39e6e3d093b45f792959ef06fea1c175263ae1a.tar.gz |
Added support of thd->tx_read_only
Moved timestamp handling from all handler::write() methods in the storage engines to handler::ha_write
sql/handler.cc:
Added PSI_CALL's
Diffstat (limited to 'sql')
-rw-r--r-- | sql/event_data_objects.cc | 4 | ||||
-rw-r--r-- | sql/event_scheduler.cc | 2 | ||||
-rw-r--r-- | sql/events.cc | 4 | ||||
-rw-r--r-- | sql/handler.cc | 123 | ||||
-rw-r--r-- | sql/handler.h | 23 | ||||
-rw-r--r-- | sql/mysqld.cc | 8 | ||||
-rw-r--r-- | sql/share/errmsg-utf8.txt | 5 | ||||
-rw-r--r-- | sql/sql_base.cc | 19 | ||||
-rw-r--r-- | sql/sql_class.cc | 7 | ||||
-rw-r--r-- | sql/sql_class.h | 102 | ||||
-rw-r--r-- | sql/sql_insert.cc | 7 | ||||
-rw-r--r-- | sql/sql_parse.cc | 184 | ||||
-rw-r--r-- | sql/sys_vars.cc | 38 | ||||
-rw-r--r-- | sql/sys_vars.h | 24 | ||||
-rw-r--r-- | sql/transaction.cc | 34 |
15 files changed, 519 insertions, 65 deletions
diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index c41194d1a8d..fd9568acf00 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -1455,6 +1455,7 @@ end: else { ulong saved_master_access; + bool save_tx_read_only; thd->set_query(sp_sql.c_ptr_safe(), sp_sql.length()); @@ -1466,9 +1467,12 @@ end: saved_master_access= thd->security_ctx->master_access; thd->security_ctx->master_access |= SUPER_ACL; + save_tx_read_only= thd->tx_read_only; + thd->tx_read_only= false; ret= Events::drop_event(thd, dbname, name, FALSE); + thd->tx_read_only= save_tx_read_only; thd->security_ctx->master_access= saved_master_access; } } diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc index 55a3f6b36c4..fae703e23c4 100644 --- a/sql/event_scheduler.cc +++ b/sql/event_scheduler.cc @@ -413,6 +413,8 @@ Event_scheduler::start() for writing when the server is running in the read-only mode. */ new_thd->security_ctx->master_access |= SUPER_ACL; + new_thd->variables.tx_read_only= false; + new_thd->tx_read_only= false; scheduler_param_value= (struct scheduler_param *)my_malloc(sizeof(struct scheduler_param), MYF(0)); diff --git a/sql/events.cc b/sql/events.cc index 8b4bab9e3a6..d403e38d784 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -1055,6 +1055,7 @@ Events::load_events_from_db(THD *thd) bool ret= TRUE; uint count= 0; ulong saved_master_access; + bool save_tx_read_only; DBUG_ENTER("Events::load_events_from_db"); DBUG_PRINT("enter", ("thd: 0x%lx", (long) thd)); @@ -1067,9 +1068,12 @@ Events::load_events_from_db(THD *thd) saved_master_access= thd->security_ctx->master_access; thd->security_ctx->master_access |= SUPER_ACL; + save_tx_read_only= thd->tx_read_only; + thd->tx_read_only= false; ret= db_repository->open_event_table(thd, TL_WRITE, &table); + thd->tx_read_only= save_tx_read_only; thd->security_ctx->master_access= saved_master_access; if (ret) diff --git a/sql/handler.cc b/sql/handler.cc index b7d545a75dc..7f1d4038af4 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -11,8 +11,8 @@ GNU General Public License for more details. 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file handler.cc @@ -230,7 +230,7 @@ handlerton *ha_checktype(THD *thd, enum legacy_db_type database_type, return NULL; } - RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + (void) RUN_HOOK(transaction, after_rollback, (thd, FALSE)); switch (database_type) { case DB_TYPE_MRG_ISAM: @@ -365,6 +365,7 @@ int ha_init_errors(void) SETMSG(HA_ERR_INDEX_CORRUPT, ER_DEFAULT(ER_INDEX_CORRUPT)); SETMSG(HA_ERR_TABLE_IN_FK_CHECK, ER_DEFAULT(ER_TABLE_IN_FK_CHECK)); SETMSG(HA_ERR_DISK_FULL, ER_DEFAULT(ER_DISK_FULL)); + SETMSG(HA_FTS_INVALID_DOCID, "Invalid InnoDB FTS Doc ID"); /* Register the error messages for use with my_error(). */ return my_error_register(get_handler_errmsgs, HA_ERR_FIRST, HA_ERR_LAST); @@ -994,11 +995,14 @@ void trans_register_ha(THD *thd, bool all, handlerton *ht_arg) { trans= &thd->transaction.all; thd->server_status|= SERVER_STATUS_IN_TRANS; + if (thd->tx_read_only) + thd->server_status|= SERVER_STATUS_IN_TRANS_READONLY; + DBUG_PRINT("info", ("setting SERVER_STATUS_IN_TRANS")); } else trans= &thd->transaction.stmt; - ha_info= thd->ha_data[ht_arg->slot].ha_info + static_cast<unsigned>(all); + ha_info= thd->ha_data[ht_arg->slot].ha_info + (all ? 1 : 0); if (ha_info->is_started()) DBUG_VOID_RETURN; /* already registered, return */ @@ -1155,6 +1159,9 @@ int ha_commit_trans(THD *thd, bool all) ER_WARNING_NOT_COMPLETE_ROLLBACK, ER(ER_WARNING_NOT_COMPLETE_ROLLBACK));); + DBUG_PRINT("info", + ("all: %d thd->in_sub_stmt: %d ha_info: %p is_real_trans: %d", + all, thd->in_sub_stmt, ha_info, is_real_trans)); /* We must not commit the normal transaction if a statement transaction is pending. Otherwise statement transaction @@ -1191,7 +1198,9 @@ int ha_commit_trans(THD *thd, bool all) if (!ha_info) { - /* Free resources and perform other cleanup even for 'empty' transactions. */ + /* + Free resources and perform other cleanup even for 'empty' transactions. + */ if (is_real_trans) thd->transaction.cleanup(); DBUG_RETURN(0); @@ -1490,7 +1499,7 @@ int ha_rollback_trans(THD *thd, bool all) push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARNING_NOT_COMPLETE_ROLLBACK, ER(ER_WARNING_NOT_COMPLETE_ROLLBACK)); - RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + (void) RUN_HOOK(transaction, after_rollback, (thd, FALSE)); DBUG_RETURN(error); } @@ -2147,6 +2156,16 @@ int ha_delete_table(THD *thd, handlerton *table_type, const char *path, ha_delete_table_error_handler.buff); } delete file; + +#ifdef HAVE_PSI_TABLE_INTERFACE + if (likely(error == 0)) + { + my_bool temp_table= (my_bool)is_prefix(alias, tmp_file_prefix); + PSI_CALL(drop_table_share)(temp_table, db, strlen(db), + alias, strlen(alias)); + } +#endif + DBUG_RETURN(error); } @@ -2214,6 +2233,30 @@ THD *handler::ha_thd(void) const return (table && table->in_use) ? table->in_use : current_thd; } +void handler::unbind_psi() +{ +#ifdef HAVE_PSI_TABLE_INTERFACE + /* + Notify the instrumentation that this table is not owned + by this thread any more. + */ + PSI_CALL(unbind_table)(m_psi); +#endif +} + +void handler::rebind_psi() +{ +#ifdef HAVE_PSI_TABLE_INTERFACE + /* + Notify the instrumentation that this table is now owned + by this thread. + */ + PSI_table_share *share_psi= ha_table_share_psi(table_share); + m_psi= PSI_CALL(rebind_table)(share_psi, this, m_psi); +#endif +} + + PSI_table_share *handler::ha_table_share_psi(const TABLE_SHARE *share) const { return share->m_psi; @@ -2256,6 +2299,13 @@ int handler::ha_open(TABLE *table_arg, const char *name, int mode, } else { + DBUG_ASSERT(m_psi == NULL); + DBUG_ASSERT(table_share != NULL); +#ifdef HAVE_PSI_TABLE_INTERFACE + PSI_table_share *share_psi= ha_table_share_psi(table_share); + m_psi= PSI_CALL(open_table)(share_psi, this); +#endif + if (table->s->db_options_in_use & HA_OPTION_READ_ONLY_DATA) table->db_stat|=HA_READ_ONLY; (void) extra(HA_EXTRA_NO_READCHECK); // Not needed in SQL @@ -2264,7 +2314,7 @@ int handler::ha_open(TABLE *table_arg, const char *name, int mode, if (!ref && !(ref= (uchar*) alloc_root(&table->mem_root, ALIGN_SIZE(ref_length)*2))) { - close(); + ha_close(); error=HA_ERR_OUT_OF_MEM; } else @@ -2276,7 +2326,7 @@ int handler::ha_open(TABLE *table_arg, const char *name, int mode, DBUG_RETURN(error); } -int handler::ha_close() +int handler::ha_close(void) { DBUG_ENTER("ha_close"); /* @@ -2285,6 +2335,11 @@ int handler::ha_close() */ if (table->in_use) status_var_add(table->in_use->status_var.rows_tmp_read, rows_tmp_read); +#ifdef HAVE_PSI_TABLE_INTERFACE + PSI_CALL(close_table)(m_psi); + m_psi= NULL; /* instrumentation handle, invalid after close_table() */ +#endif + DBUG_RETURN(close()); } @@ -2718,7 +2773,7 @@ void handler::get_auto_increment(ulonglong offset, ulonglong increment, ha_index_init(table->s->next_number_index, 1); if (table->s->next_number_keypart == 0) { // Autoincrement at key-start - error=ha_index_last(table->record[1]); + error= ha_index_last(table->record[1]); /* MySQL implicitely assumes such method does locking (as MySQL decides to use nr+increment without checking again with the handler, in @@ -3978,10 +4033,19 @@ int ha_create_table(THD *thd, const char *path, const char *name; TABLE_SHARE share; DBUG_ENTER("ha_create_table"); +#ifdef HAVE_PSI_TABLE_INTERFACE + my_bool temp_table= (my_bool)is_prefix(table_name, tmp_file_prefix) || + (create_info->options & HA_LEX_CREATE_TMP_TABLE ? TRUE : FALSE); +#endif init_tmp_table_share(thd, &share, db, 0, table_name, path); - if (open_table_def(thd, &share, 0) || - open_table_from_share(thd, &share, "", 0, (uint) READ_ALL, 0, &table, + if (open_table_def(thd, &share, 0)) + goto err; + +#ifdef HAVE_PSI_TABLE_INTERFACE + share.m_psi= PSI_CALL(get_table_share)(temp_table, &share); +#endif + if (open_table_from_share(thd, &share, "", 0, (uint) READ_ALL, 0, &table, TRUE)) goto err; @@ -3996,6 +4060,10 @@ int ha_create_table(THD *thd, const char *path, { strxmov(name_buff, db, ".", table_name, NullS); my_error(ER_CANT_CREATE_TABLE, MYF(ME_BELL+ME_WAITTANG), name_buff, error); +#ifdef HAVE_PSI_TABLE_INTERFACE + PSI_CALL(drop_table_share)(temp_table, db, strlen(db), table_name, + strlen(table_name)); +#endif } err: free_table_share(&share); @@ -4051,6 +4119,15 @@ int ha_create_table_from_engine(THD* thd, const char *db, const char *name) { DBUG_RETURN(3); } + +#ifdef HAVE_PSI_TABLE_INTERFACE + /* + Table discovery is not instrumented. + Once discovered, the table will be opened normally, + and instrumented normally. + */ +#endif + if (open_table_from_share(thd, &share, "" ,0, 0, 0, &table, FALSE)) { free_table_share(&share); @@ -4973,6 +5050,7 @@ static int binlog_log_row(TABLE* table, int handler::ha_external_lock(THD *thd, int lock_type) { + int error; DBUG_ENTER("handler::ha_external_lock"); /* Whether this is lock or unlock, this should be true, and is to verify that @@ -5002,11 +5080,14 @@ int handler::ha_external_lock(THD *thd, int lock_type) } } + ha_statistic_increment(&SSV::ha_external_lock_count); + /* We cache the table flags if the locking succeeded. Otherwise, we keep them as they were when they were fetched in ha_open(). */ - int error= external_lock(thd, lock_type); + MYSQL_TABLE_LOCK_WAIT(m_psi, PSI_TABLE_EXTERNAL_LOCK, lock_type, + { error= external_lock(thd, lock_type); }) if (error == 0) cached_table_flags= table_flags(); @@ -5064,11 +5145,17 @@ int handler::ha_write_row(uchar *buf) Log_func *log_func= Write_rows_log_event::binlog_row_logging_function; DBUG_ENTER("handler::ha_write_row"); + /* If we have a timestamp column, update it to the current time */ + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); + MYSQL_INSERT_ROW_START(table_share->db.str, table_share->table_name.str); mark_trx_read_write(); increment_statistics(&SSV::ha_write_count); - error= write_row(buf); + MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0, + { error= write_row(buf); }) + MYSQL_INSERT_ROW_DONE(error); if (unlikely(error)) DBUG_RETURN(error); @@ -5090,11 +5177,16 @@ int handler::ha_update_row(const uchar *old_data, uchar *new_data) */ DBUG_ASSERT(new_data == table->record[0]); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); + MYSQL_UPDATE_ROW_START(table_share->db.str, table_share->table_name.str); mark_trx_read_write(); increment_statistics(&SSV::ha_update_count); - error= update_row(old_data, new_data); + MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_UPDATE_ROW, active_index, 0, + { error= update_row(old_data, new_data);}) + MYSQL_UPDATE_ROW_DONE(error); if (unlikely(error)) return error; @@ -5113,7 +5205,8 @@ int handler::ha_delete_row(const uchar *buf) mark_trx_read_write(); increment_statistics(&SSV::ha_delete_count); - error= delete_row(buf); + MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_DELETE_ROW, active_index, 0, + { error= delete_row(buf);}) MYSQL_DELETE_ROW_DONE(error); if (unlikely(error)) return error; diff --git a/sql/handler.h b/sql/handler.h index 981bf9aec6a..da57f1a19a4 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -326,8 +326,24 @@ #define HA_CACHE_TBL_ASKTRANSACT 2 #define HA_CACHE_TBL_TRANSACT 4 -/* Options of START TRANSACTION statement (and later of SET TRANSACTION stmt) */ -#define MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT 1 +/** + Options for the START TRANSACTION statement. + + Note that READ ONLY and READ WRITE are logically mutually exclusive. + This is enforced by the parser and depended upon by trans_begin(). + + We need two flags instead of one in order to differentiate between + situation when no READ WRITE/ONLY clause were given and thus transaction + is implicitly READ WRITE and the case when READ WRITE clause was used + explicitly. +*/ + +// WITH CONSISTENT SNAPSHOT option +static const uint MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT = 1; +// READ ONLY option +static const uint MYSQL_START_TRANS_OPT_READ_ONLY = 2; +// READ WRITE option +static const uint MYSQL_START_TRANS_OPT_READ_WRITE = 4; /* Flags for method is_fatal_error */ #define HA_CHECK_DUP_KEY 1 @@ -1840,6 +1856,9 @@ public: */ PSI_table *m_psi; + virtual void unbind_psi(); + virtual void rebind_psi(); + handler(handlerton *ht_arg, TABLE_SHARE *share_arg) :table_share(share_arg), table(0), estimation_rows_to_insert(0), ht(ht_arg), diff --git a/sql/mysqld.cc b/sql/mysqld.cc index f81c4127fde..ef0de79820d 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -6453,6 +6453,12 @@ struct my_option my_long_options[]= &global_system_variables.tx_isolation, &global_system_variables.tx_isolation, &tx_isolation_typelib, GET_ENUM, REQUIRED_ARG, ISO_REPEATABLE_READ, 0, 0, 0, 0, 0}, + {"transaction-read-only", 0, + "Default transaction access mode. " + "True if transactions are read-only.", + &global_system_variables.tx_read_only, + &global_system_variables.tx_read_only, 0, + GET_BOOL, OPT_ARG, 0, 0, 0, 0, 0, 0}, {"user", 'u', "Run mysqld daemon as user.", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"verbose", 'v', "Used with --help option for detailed help.", @@ -6946,7 +6952,7 @@ SHOW_VAR status_vars[]= { {"Handler_commit", (char*) offsetof(STATUS_VAR, ha_commit_count), SHOW_LONG_STATUS}, {"Handler_delete", (char*) offsetof(STATUS_VAR, ha_delete_count), SHOW_LONG_STATUS}, {"Handler_discover", (char*) offsetof(STATUS_VAR, ha_discover_count), SHOW_LONG_STATUS}, - + {"Handler_external_lock", (char*) offsetof(STATUS_VAR, ha_external_lock_count), SHOW_LONGLONG_STATUS}, {"Handler_icp_attempts", (char*) offsetof(STATUS_VAR, ha_icp_attempts), SHOW_LONG_STATUS}, {"Handler_icp_match", (char*) offsetof(STATUS_VAR, ha_icp_match), SHOW_LONG_STATUS}, diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 907161224d5..eb3e5c91262 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -5981,9 +5981,8 @@ ER_WRONG_PARTITION_NAME eng "Incorrect partition name" ger "Falscher Partitionsname" swe "Felaktigt partitionsnamn" -ER_CANT_CHANGE_TX_ISOLATION 25001 - eng "Transaction isolation level can't be changed while a transaction is in progress" - ger "Transaktionsisolationsebene kann während einer laufenden Transaktion nicht geändert werden" +ER_CANT_CHANGE_TX_CHARACTERISTICS 25001 + eng "Transaction characteristics can't be changed while a transaction is in progress" ER_DUP_ENTRY_AUTOINCREMENT_CASE eng "ALTER TABLE causes auto_increment resequencing, resulting in duplicate entry '%-.192s' for key '%-.192s'" ger "ALTER TABLE führt zur Neusequenzierung von auto_increment, wodurch der doppelte Eintrag '%-.192s' für Schlüssel '%-.192s' auftritt" diff --git a/sql/sql_base.cc b/sql/sql_base.cc index acd330dd4d2..fadcd64b98e 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2908,6 +2908,16 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK)) { /* + Check if we're trying to take a write lock in a read only transaction. + */ + if (table_list->mdl_request.type >= MDL_SHARED_WRITE && + thd->tx_read_only) + { + my_error(ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION, MYF(0)); + DBUG_RETURN(true); + } + + /* We are not under LOCK TABLES and going to acquire write-lock/ modify the base table. We need to acquire protection against global read lock until end of this statement in order to have @@ -4706,6 +4716,15 @@ lock_table_names(THD *thd, ! (flags & MYSQL_OPEN_SKIP_TEMPORARY) && find_temporary_table(thd, table)))) { + /* + Write lock on normal tables is not allowed in a read only transaction. + */ + if (thd->tx_read_only) + { + my_error(ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION, MYF(0)); + return true; + } + if (! (flags & MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK) && schema_set.insert(table)) DBUG_RETURN(TRUE); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 0165aa9f6e5..67a9312b6e6 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -593,6 +593,12 @@ int thd_tx_isolation(const THD *thd) } extern "C" +int thd_tx_is_read_only(const THD *thd) +{ + return (int) thd->tx_read_only; +} + +extern "C" void thd_inc_row_count(THD *thd) { thd->warning_info->inc_current_row_for_warning(); @@ -1178,6 +1184,7 @@ void THD::init(void) TL_WRITE_LOW_PRIORITY : TL_WRITE); tx_isolation= (enum_tx_isolation) variables.tx_isolation; + tx_read_only= variables.tx_read_only; update_charset(); reset_current_stmt_binlog_format_row(); bzero((char *) &status_var, sizeof(status_var)); diff --git a/sql/sql_class.h b/sql/sql_class.h index de5c0583213..9a4a3dece67 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -43,7 +43,7 @@ #include "violite.h" /* vio_is_connected */ #include "thr_lock.h" /* thr_lock_type, THR_LOCK_DATA, THR_LOCK_INFO */ - +#include <mysql/psi/mysql_table.h> class Reprepare_observer; class Relay_log_info; @@ -537,6 +537,10 @@ typedef struct system_variables */ my_thread_id pseudo_thread_id; + /** + Default transaction access mode. READ ONLY (true) or READ WRITE (false). + */ + my_bool tx_read_only; my_bool low_priority_updates; my_bool query_cache_wlock_invalidate; my_bool engine_condition_pushdown; @@ -619,6 +623,7 @@ typedef struct system_status_var ulong ha_discover_count; ulong ha_savepoint_count; ulong ha_savepoint_rollback_count; + ulong ha_external_lock_count; #if 0 /* KEY_CACHE parts. These are copies of the original */ @@ -2175,6 +2180,11 @@ public: above. */ enum_tx_isolation tx_isolation; + /* + Current or next transaction access mode. + See comment above regarding tx_isolation. + */ + bool tx_read_only; enum_check_fields count_cuted_fields; DYNAMIC_ARRAY user_var_events; /* For user variables replication */ @@ -4083,6 +4093,32 @@ public: */ #define CF_CAN_GENERATE_ROW_EVENTS (1U << 9) +/** + Identifies statements which may deal with temporary tables and for which + temporary tables should be pre-opened to simplify privilege checks. +*/ +#define CF_PREOPEN_TMP_TABLES (1U << 10) + +/** + Identifies statements for which open handlers should be closed in the + beginning of the statement. +*/ +#define CF_HA_CLOSE (1U << 11) + +/** + Identifies statements that can be explained with EXPLAIN. +*/ +#define CF_CAN_BE_EXPLAINED (1U << 12) + +/** Identifies statements which may generate an optimizer trace */ +#define CF_OPTIMIZER_TRACE (1U << 14) + +/** + Identifies statements that should always be disallowed in + read only transactions. +*/ +#define CF_DISALLOW_IN_RO_TRANS (1U << 15) + /* Bits in server_command_flags */ /** @@ -4144,14 +4180,15 @@ inline int handler::ha_index_read_map(uchar * buf, const uchar * key, key_part_map keypart_map, enum ha_rkey_function find_flag) { + int error; DBUG_ASSERT(inited==INDEX); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); increment_statistics(&SSV::ha_read_key_count); - int error= index_read_map(buf, key, keypart_map, find_flag); + MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, active_index, 0, + { error= index_read_map(buf, key, keypart_map, + find_flag); }) if (!error) update_index_statistics(); table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_INDEX_READ_ROW_DONE(error); return error; } @@ -4167,83 +4204,84 @@ inline int handler::ha_index_read_idx_map(uchar * buf, uint index, key_part_map keypart_map, enum ha_rkey_function find_flag) { + int error; DBUG_ASSERT(inited==NONE); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); increment_statistics(&SSV::ha_read_key_count); - int error= index_read_idx_map(buf, index, key, keypart_map, find_flag); + MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, index, 0, + { error= index_read_idx_map(buf, index, key, keypart_map, + find_flag); }) if (!error) { update_rows_read(); index_rows_read[index]++; } table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_INDEX_READ_ROW_DONE(error); return error; } inline int handler::ha_index_next(uchar * buf) { + int error; DBUG_ASSERT(inited==INDEX); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); increment_statistics(&SSV::ha_read_next_count); - int error= index_next(buf); + MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, active_index, 0, + { error= index_next(buf); }) if (!error) update_index_statistics(); table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_INDEX_READ_ROW_DONE(error); return error; } inline int handler::ha_index_prev(uchar * buf) { + int error; DBUG_ASSERT(inited==INDEX); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); increment_statistics(&SSV::ha_read_prev_count); - int error= index_prev(buf); + MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, active_index, 0, + { error= index_prev(buf); }) if (!error) update_index_statistics(); table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_INDEX_READ_ROW_DONE(error); return error; } inline int handler::ha_index_first(uchar * buf) { + int error; DBUG_ASSERT(inited==INDEX); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); increment_statistics(&SSV::ha_read_first_count); - int error= index_first(buf); + MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, active_index, 0, + { error= index_first(buf); }) if (!error) update_index_statistics(); table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_INDEX_READ_ROW_DONE(error); return error; } inline int handler::ha_index_last(uchar * buf) { + int error; DBUG_ASSERT(inited==INDEX); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); increment_statistics(&SSV::ha_read_last_count); - int error= index_last(buf); + MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, active_index, 0, + { error= index_last(buf); }) if (!error) update_index_statistics(); table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_INDEX_READ_ROW_DONE(error); return error; } inline int handler::ha_index_next_same(uchar *buf, const uchar *key, uint keylen) { + int error; DBUG_ASSERT(inited==INDEX); - MYSQL_INDEX_READ_ROW_START(table_share->db.str, table_share->table_name.str); increment_statistics(&SSV::ha_read_next_count); - int error= index_next_same(buf, key, keylen); + MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, active_index, 0, + { error= index_next_same(buf, key, keylen); }) if (!error) update_index_statistics(); table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_INDEX_READ_ROW_DONE(error); return error; } @@ -4259,8 +4297,9 @@ inline int handler::ha_ft_read(uchar *buf) inline int handler::ha_rnd_next(uchar *buf) { - MYSQL_READ_ROW_START(table_share->db.str, table_share->table_name.str, TRUE); - int error= rnd_next(buf); + int error; + MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, MAX_KEY, 0, + { error= rnd_next(buf); }) if (!error) { update_rows_read(); @@ -4272,19 +4311,18 @@ inline int handler::ha_rnd_next(uchar *buf) increment_statistics(&SSV::ha_read_rnd_next_count); table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_READ_ROW_DONE(error); return error; } inline int handler::ha_rnd_pos(uchar *buf, uchar *pos) { - MYSQL_READ_ROW_START(table_share->db.str, table_share->table_name.str, FALSE); + int error; increment_statistics(&SSV::ha_read_rnd_count); - int error= rnd_pos(buf, pos); + MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_FETCH_ROW, MAX_KEY, 0, + { error= rnd_pos(buf, pos); }) if (!error) update_rows_read(); table->status=error ? STATUS_NOT_FOUND: 0; - MYSQL_READ_ROW_DONE(error); return error; } @@ -4308,18 +4346,22 @@ inline int handler::ha_read_first_row(uchar *buf, uint primary_key) inline int handler::ha_write_tmp_row(uchar *buf) { + int error; MYSQL_INSERT_ROW_START(table_share->db.str, table_share->table_name.str); increment_statistics(&SSV::ha_tmp_write_count); - int error= write_row(buf); + MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0, + { error= write_row(buf); }) MYSQL_INSERT_ROW_DONE(error); return error; } inline int handler::ha_update_tmp_row(const uchar *old_data, uchar *new_data) { + int error; MYSQL_UPDATE_ROW_START(table_share->db.str, table_share->table_name.str); increment_statistics(&SSV::ha_tmp_update_count); - int error= update_row(old_data, new_data); + MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_UPDATE_ROW, active_index, 0, + { error= update_row(old_data, new_data);}) MYSQL_UPDATE_ROW_DONE(error); return error; } diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 54f94ce78c1..adc3d9d875a 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -553,6 +553,13 @@ bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list) DBUG_ENTER("open_and_lock_for_insert_delayed"); #ifndef EMBEDDED_LIBRARY + /* INSERT DELAYED is not allowed in a read only transaction. */ + if (thd->tx_read_only) + { + my_error(ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION, MYF(0)); + DBUG_RETURN(true); + } + /* In order for the deadlock detector to be able to find any deadlocks caused by the handler thread waiting for GRL or this table, we acquire diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 17a6cf0f379..19a8ba2b533 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -291,26 +291,54 @@ void init_update_queries(void) sql_command_flags[SQLCOM_DROP_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_UPDATE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED; sql_command_flags[SQLCOM_UPDATE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED; sql_command_flags[SQLCOM_INSERT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED; sql_command_flags[SQLCOM_INSERT_SELECT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED; sql_command_flags[SQLCOM_DELETE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED; sql_command_flags[SQLCOM_DELETE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED;; sql_command_flags[SQLCOM_REPLACE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED;; sql_command_flags[SQLCOM_REPLACE_SELECT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED; sql_command_flags[SQLCOM_SELECT]= CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; - sql_command_flags[SQLCOM_SET_OPTION]= CF_REEXECUTION_FRAGILE | CF_AUTO_COMMIT_TRANS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED; + // (1) so that subquery is traced when doing "SET @var = (subquery)" + /* + @todo SQLCOM_SET_OPTION should have CF_CAN_GENERATE_ROW_EVENTS + set, because it may invoke a stored function that generates row + events. /Sven + */ + sql_command_flags[SQLCOM_SET_OPTION]= CF_REEXECUTION_FRAGILE | + CF_AUTO_COMMIT_TRANS | + CF_OPTIMIZER_TRACE; // (1) + // (1) so that subquery is traced when doing "DO @var := (subquery)" sql_command_flags[SQLCOM_DO]= CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE; // (1) sql_command_flags[SQLCOM_SHOW_STATUS_PROC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_STATUS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; @@ -351,6 +379,11 @@ void init_update_queries(void) sql_command_flags[SQLCOM_SHOW_CREATE_EVENT]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_PROFILES]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_PROFILE]= CF_STATUS_COMMAND; + /* + @todo SQLCOM_BINLOG_BASE64_EVENT should have + CF_CAN_GENERATE_ROW_EVENTS set, because this surely generates row + events. /Sven + */ sql_command_flags[SQLCOM_BINLOG_BASE64_EVENT]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CLIENT_STATS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_USER_STATS]= CF_STATUS_COMMAND; @@ -366,6 +399,12 @@ void init_update_queries(void) sql_command_flags[SQLCOM_GRANT]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_REVOKE]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_OPTIMIZE]= CF_CHANGES_DATA; + /* + @todo SQLCOM_CREATE_FUNCTION should have CF_AUTO_COMMIT_TRANS + set. this currently is binlogged *before* the transaction if + executed inside a transaction because it does not have an implicit + pre-commit and is written to the statement cache. /Sven + */ sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; @@ -382,8 +421,13 @@ void init_update_queries(void) last called (or executed) statement is preserved. See mysql_execute_command() for how CF_ROW_COUNT is used. */ + /* + (1): without it, in "CALL some_proc((subq))", subquery would not be + traced. + */ sql_command_flags[SQLCOM_CALL]= CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE; // (1) sql_command_flags[SQLCOM_EXECUTE]= CF_CAN_GENERATE_ROW_EVENTS; /* @@ -411,6 +455,104 @@ void init_update_queries(void) sql_command_flags[SQLCOM_CREATE_SERVER]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_ALTER_SERVER]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_SERVER]= CF_AUTO_COMMIT_TRANS; + + /* + The following statements can deal with temporary tables, + so temporary tables should be pre-opened for those statements to + simplify privilege checking. + + There are other statements that deal with temporary tables and open + them, but which are not listed here. The thing is that the order of + pre-opening temporary tables for those statements is somewhat custom. + */ + sql_command_flags[SQLCOM_CREATE_TABLE]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_DROP_TABLE]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_CREATE_INDEX]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_ALTER_TABLE]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_TRUNCATE]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_LOAD]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_DROP_INDEX]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_UPDATE]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_UPDATE_MULTI]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_INSERT_SELECT]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_DELETE]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_DELETE_MULTI]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_REPLACE_SELECT]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_SELECT]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_SET_OPTION]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_DO]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_CALL]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_CHECKSUM]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_ANALYZE]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_CHECK]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_OPTIMIZE]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_REPAIR]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_PRELOAD_KEYS]|= CF_PREOPEN_TMP_TABLES; + sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]|= CF_PREOPEN_TMP_TABLES; + + /* + DDL statements that should start with closing opened handlers. + + We use this flag only for statements for which open HANDLERs + have to be closed before emporary tables are pre-opened. + */ + sql_command_flags[SQLCOM_CREATE_TABLE]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_DROP_TABLE]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_ALTER_TABLE]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_TRUNCATE]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_REPAIR]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_OPTIMIZE]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_ANALYZE]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_CHECK]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_CREATE_INDEX]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_DROP_INDEX]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_PRELOAD_KEYS]|= CF_HA_CLOSE; + sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]|= CF_HA_CLOSE; + + /* + Mark statements that always are disallowed in read-only + transactions. Note that according to the SQL standard, + even temporary table DDL should be disallowed. + */ + sql_command_flags[SQLCOM_CREATE_TABLE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_ALTER_TABLE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_TABLE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_RENAME_TABLE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_INDEX]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_INDEX]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_DB]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_DB]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_ALTER_DB]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_VIEW]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_VIEW]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_TRIGGER]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_TRIGGER]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_EVENT]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_ALTER_EVENT]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_EVENT]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_USER]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_RENAME_USER]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_USER]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_SERVER]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_ALTER_SERVER]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_SERVER]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_FUNCTION]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_PROCEDURE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_SPFUNCTION]|=CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_PROCEDURE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_FUNCTION]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_ALTER_PROCEDURE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_ALTER_FUNCTION]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_TRUNCATE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_ALTER_TABLESPACE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_REPAIR]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_OPTIMIZE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_GRANT]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_REVOKE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_REVOKE_ALL]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_INSTALL_PLUGIN]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]|= CF_DISALLOW_IN_RO_TRANS; } bool sqlcom_can_generate_row_events(const THD *thd) @@ -2115,6 +2257,19 @@ mysql_execute_command(THD *thd) DEBUG_SYNC(thd,"before_execute_sql_command"); #endif + /* + Check if we are in a read-only transaction and we're trying to + execute a statement which should always be disallowed in such cases. + + Note that this check is done after any implicit commits. + */ + if (thd->tx_read_only && + (sql_command_flags[lex->sql_command] & CF_DISALLOW_IN_RO_TRANS)) + { + my_error(ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION, MYF(0)); + goto error; + } + switch (lex->sql_command) { case SQLCOM_SHOW_EVENTS: @@ -3766,12 +3921,13 @@ end_with_restore_list: if (tx_chain) { if (trans_begin(thd)) - goto error; + goto error; } else { /* Reset the isolation level if no chaining transaction. */ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; } /* Disconnect the current client connection. */ if (tx_release) @@ -4325,6 +4481,7 @@ create_sp_error: isolation level to the session default. */ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; my_ok(thd); break; case SQLCOM_XA_ROLLBACK: @@ -4336,6 +4493,7 @@ create_sp_error: isolation level to the session default. */ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; my_ok(thd); break; case SQLCOM_XA_RECOVER: diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 4df3773e5ce..97fbe76f149 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -2366,7 +2366,7 @@ static bool check_tx_isolation(sys_var *self, THD *thd, set_var *var) if (var->type == OPT_DEFAULT && thd->in_active_multi_stmt_transaction()) { DBUG_ASSERT(thd->in_multi_stmt_transaction_mode()); - my_error(ER_CANT_CHANGE_TX_ISOLATION, MYF(0)); + my_error(ER_CANT_CHANGE_TX_CHARACTERISTICS, MYF(0)); return TRUE; } return FALSE; @@ -2379,6 +2379,42 @@ static Sys_var_tx_isolation Sys_tx_isolation( tx_isolation_names, DEFAULT(ISO_REPEATABLE_READ), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_tx_isolation)); + +/** + Can't change the tx_read_only state if we are already in a + transaction. +*/ + +static bool check_tx_read_only(sys_var *self, THD *thd, set_var *var) +{ + if (var->type == OPT_DEFAULT && thd->in_active_multi_stmt_transaction()) + { + DBUG_ASSERT(thd->in_multi_stmt_transaction_mode()); + my_error(ER_CANT_CHANGE_TX_CHARACTERISTICS, MYF(0)); + return true; + } + return false; +} + + +bool Sys_var_tx_read_only::session_update(THD *thd, set_var *var) +{ + if (var->type == OPT_SESSION && Sys_var_mybool::session_update(thd, var)) + return true; + if (var->type == OPT_DEFAULT || !thd->in_active_multi_stmt_transaction()) + { + // @see Sys_var_tx_isolation::session_update() above for the rules. + thd->tx_read_only= var->save_result.ulonglong_value; + } + return false; +} + + +static Sys_var_tx_read_only Sys_tx_read_only( + "tx_read_only", "Set default transaction access mode to read only.", + SESSION_VAR(tx_read_only), NO_CMD_LINE, DEFAULT(0), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_tx_read_only)); + static Sys_var_ulonglong Sys_tmp_table_size( "tmp_table_size", "If an internal in-memory temporary table exceeds this size, MySQL " diff --git a/sql/sys_vars.h b/sql/sys_vars.h index c680ce6ab51..b594ff35571 100644 --- a/sql/sys_vars.h +++ b/sql/sys_vars.h @@ -1840,6 +1840,30 @@ public: } }; + +/** + Class representing the tx_read_only system variable for setting + default transaction access mode. + + Note that there is a special syntax - SET TRANSACTION READ ONLY + (or READ WRITE) that sets the access mode for the next transaction + only. +*/ + +class Sys_var_tx_read_only: public Sys_var_mybool +{ +public: + Sys_var_tx_read_only(const char *name_arg, const char *comment, int flag_args, + ptrdiff_t off, size_t size, CMD_LINE getopt, + my_bool def_val, PolyLock *lock, + enum binlog_status_enum binlog_status_arg, + on_check_function on_check_func) + :Sys_var_mybool(name_arg, comment, flag_args, off, size, getopt, + def_val, lock, binlog_status_arg, on_check_func) + {} + virtual bool session_update(THD *thd, set_var *var); +}; + /* Class for replicate_events_marked_for_skip. We need a custom update function that ensures the slave is stopped when diff --git a/sql/transaction.cc b/sql/transaction.cc index 3359decbcd5..277a95b90a9 100644 --- a/sql/transaction.cc +++ b/sql/transaction.cc @@ -22,6 +22,7 @@ #include "transaction.h" #include "rpl_handler.h" #include "debug_sync.h" // DEBUG_SYNC +#include "sql_acl.h" /* Conditions under which the transaction state must not change. */ static bool trans_check(THD *thd) @@ -150,9 +151,35 @@ bool trans_begin(THD *thd, uint flags) */ thd->mdl_context.release_transactional_locks(); + // The RO/RW options are mutually exclusive. + DBUG_ASSERT(!((flags & MYSQL_START_TRANS_OPT_READ_ONLY) && + (flags & MYSQL_START_TRANS_OPT_READ_WRITE))); + if (flags & MYSQL_START_TRANS_OPT_READ_ONLY) + thd->tx_read_only= true; + else if (flags & MYSQL_START_TRANS_OPT_READ_WRITE) + { + /* + Explicitly starting a RW transaction when the server is in + read-only mode, is not allowed unless the user has SUPER priv. + Implicitly starting a RW transaction is allowed for backward + compatibility. + */ + const bool user_is_super= + test(thd->security_ctx->master_access & SUPER_ACL); + if (opt_readonly && !user_is_super) + { + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only"); + DBUG_RETURN(true); + } + thd->tx_read_only= false; + } + thd->variables.option_bits|= OPTION_BEGIN; thd->server_status|= SERVER_STATUS_IN_TRANS; + if (thd->tx_read_only) + thd->server_status|= SERVER_STATUS_IN_TRANS_READONLY; + /* ha_start_consistent_snapshot() relies on OPTION_BEGIN flag set. */ if (flags & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT) res= ha_start_consistent_snapshot(thd); @@ -234,6 +261,7 @@ bool trans_commit_implicit(THD *thd) to not have any effect on implicit commit. */ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; DBUG_RETURN(res); } @@ -298,7 +326,10 @@ bool trans_commit_stmt(THD *thd) { res= ha_commit_trans(thd, FALSE); if (! thd->in_active_multi_stmt_transaction()) + { thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; + } } if (res) @@ -342,7 +373,10 @@ bool trans_rollback_stmt(THD *thd) if (thd->transaction_rollback_request && !thd->in_sub_stmt) ha_rollback_trans(thd, TRUE); if (! thd->in_active_multi_stmt_transaction()) + { thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; + } } RUN_HOOK(transaction, after_rollback, (thd, FALSE)); |