From: magnus.blaudd Date: June 9 2011 12:18pm Subject: bzr commit into mysql-5.5-cluster branch (magnus.blaudd:3354) Bug#37153 WL#5906 List-Archive: http://lists.mysql.com/commits/138948 X-Bug: 37153 MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============8211551407043011866==" --===============8211551407043011866== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At file:///data0/magnus/mysql/5.5-cluster-rbwr/ based on revid:magnus.blaudd@stripped 3354 magnus.blaudd@stripped 2011-06-09 WL#5906 read before write removal (RBWR) - Add MCP patch for read removal in MySQL Servers update and delete loops. - Slightly modified version compared to 7.0 where this version checks the limitations of the algorithm in the MySQL Server code and only the limitations of NDB is done in ha_ndbcluster - Update test result slightly after merging in patch for bug#37153 rewrite modified: mysql-test/suite/ndb/r/ndb_update_no_read.result mysql-test/suite/ndb/t/disabled.def sql/ha_ndbcluster.cc sql/ha_ndbcluster.h sql/ha_ndbcluster_glue.h sql/handler.h sql/sql_delete.cc sql/sql_update.cc === modified file 'mysql-test/suite/ndb/r/ndb_update_no_read.result' --- a/mysql-test/suite/ndb/r/ndb_update_no_read.result 2010-12-01 12:04:27 +0000 +++ b/mysql-test/suite/ndb/r/ndb_update_no_read.result 2011-06-09 12:14:22 +0000 @@ -294,9 +294,10 @@ affected rows: 1 # 1 warning update t1 set b='one plus one' where a=2; +affected rows: 1 +info: Rows matched: 1 Changed: 1 Warnings: 1 Warnings: Warning 1265 Data truncated for column 'b' at row 1 -affected rows: 1 @ndb_execute_count:=VARIABLE_VALUE-@ndb_init_execute_count 1 affected rows: 1 @@ -367,9 +368,10 @@ affected rows: 1 begin; affected rows: 0 update t1 set b='one plus one' where a=2; +affected rows: 1 +info: Rows matched: 1 Changed: 1 Warnings: 1 Warnings: Warning 1265 Data truncated for column 'b' at row 1 -affected rows: 1 commit; affected rows: 0 @ndb_execute_count:=VARIABLE_VALUE-@ndb_init_execute_count === modified file 'mysql-test/suite/ndb/t/disabled.def' --- a/mysql-test/suite/ndb/t/disabled.def 2011-05-09 08:49:19 +0000 +++ b/mysql-test/suite/ndb/t/disabled.def 2011-06-09 12:14:22 +0000 @@ -16,9 +16,6 @@ ndb_partition_error2 : Bug#40989 ndb_par ndb_cache_trans : Bug#42197 Query cache and autocommit ndb_disconnect_ddl : Bug#31853 flaky testcase... -ndb_bulk_delete : SEAGULL rbwr -ndb_update_no_read : SEAGULL rbwr - ndb_condition_pushdown : SEAGULL ndb_dd_disk2memory : SEAGULL alter === modified file 'sql/ha_ndbcluster.cc' --- a/sql/ha_ndbcluster.cc 2011-06-09 09:38:31 +0000 +++ b/sql/ha_ndbcluster.cc 2011-06-09 12:14:22 +0000 @@ -6287,39 +6287,49 @@ bool ha_ndbcluster::read_before_write_re { THD *thd= table->in_use; DBUG_ENTER("read_before_write_removal_possible"); - /* - We need to verify a large number of things before accepting to remove - the read before the update. We cannot avoid read before when primary - key is updated, when a unique key is updated, when a BLOB is updated, - for deletes on tables with BLOB's it is also not possible to avoid - the read before the update and finally it is necessary that the - update expressions only contain constant expressions. - */ - if (uses_blob_value(table->write_set) || - (thd->lex->sql_command == SQLCOM_DELETE && - table_share->blob_fields) || - (table_share->primary_key != MAX_KEY && - bitmap_is_overlapping(table->write_set, m_pk_bitmap_p))) + + if (uses_blob_value(table->write_set)) { - DBUG_RETURN(FALSE); + DBUG_PRINT("exit", ("No! Blob field in write_set")); + DBUG_RETURN(false); + } + + if (thd->lex->sql_command == SQLCOM_DELETE && + table_share->blob_fields) + { + DBUG_PRINT("exit", ("No! DELETE from table with blob(s)")); + DBUG_RETURN(false); + } + + if (table_share->primary_key == MAX_KEY) + { + DBUG_PRINT("exit", ("No! Table with hidden key")); + DBUG_RETURN(false); } + + if (bitmap_is_overlapping(table->write_set, m_pk_bitmap_p)) + { + DBUG_PRINT("exit", ("No! Updating primary key")); + DBUG_RETURN(false); + } + if (m_has_unique_index) { - KEY *key; for (uint i= 0; i < table_share->keys; i++) { - key= table->key_info + i; + const KEY* key= table->key_info + i; if ((key->flags & HA_NOSAME) && bitmap_is_overlapping(table->write_set, m_key_fields[i])) { - DBUG_RETURN(FALSE); + DBUG_PRINT("exit", ("No! Unique key %d is updated", i)); + DBUG_RETURN(false); } } } - DBUG_PRINT("info", ("read_before_write_removal_possible TRUE")); m_read_before_write_removal_possible= TRUE; - DBUG_RETURN(TRUE); + DBUG_PRINT("exit", ("Yes, rbwr is possible!")); + DBUG_RETURN(true); } @@ -9623,19 +9633,6 @@ ha_ndbcluster::~ha_ndbcluster() DBUG_VOID_RETURN; } -#ifndef NDB_WITHOUT_READ_BEFORE_WRITE_REMOVAL -void -ha_ndbcluster::column_bitmaps_signal(uint sig_type) -{ - DBUG_ENTER("column_bitmaps_signal"); - DBUG_PRINT("enter", ("read_set: 0x%lx write_set: 0x%lx", - (long) table->read_set->bitmap[0], - (long) table->write_set->bitmap[0])); - if (sig_type & HA_COMPLETE_TABLE_READ_BITMAP) - bitmap_copy(&m_save_read_set, table->read_set); - DBUG_VOID_RETURN; -} -#endif /** Open a table for further use @@ -9659,11 +9656,6 @@ int ha_ndbcluster::open(const char *name DBUG_PRINT("enter", ("name: %s mode: %d test_if_locked: %d", name, mode, test_if_locked)); - if (bitmap_init(&m_save_read_set, NULL, table_share->fields, FALSE)) - { - DBUG_RETURN(1); - } - if (table_share->primary_key != MAX_KEY) { /* @@ -9949,7 +9941,6 @@ void ha_ndbcluster::local_close(THD *thd my_free((char*)m_key_fields, MYF(0)); m_key_fields= NULL; } - bitmap_free(&m_save_read_set); if (m_share) { /* ndb_share reference handler free */ @@ -12392,40 +12383,20 @@ ha_ndbcluster::null_value_index_search(K void ha_ndbcluster::check_read_before_write_removal() { - bool use_removal= TRUE; DBUG_ENTER("check_read_before_write_removal"); - DBUG_ASSERT(m_read_before_write_removal_possible); - /* - We are doing an update or delete and it is possible that we - can ignore the read before the update or delete. This is - possible here since we are not updating the primary key and - if the index used is unique or primary and if the WHERE clause - only involves fields from this index we are ok to go. At this - moment we can only updates where all SET expressions are - constants. Thus no read set will come from SET expressions. - */ - if (table_share->primary_key == active_index) - { - if (!bitmap_cmp(&m_save_read_set, m_pk_bitmap_p)) - use_removal= FALSE; - } - else - { - KEY *key= table->key_info + active_index; - if (!(key->flags & HA_NOSAME)) - { - /* Optimisation not applicable on non-unique indexes */ - use_removal= FALSE; - } - else if (!bitmap_cmp(&m_save_read_set, - m_key_fields[active_index])) - { - use_removal= FALSE; - } - } - m_read_before_write_removal_used= use_removal; - DBUG_PRINT("info", ("m_read_before_write_removal_used: %d", - m_read_before_write_removal_used)); + + /* Must have determined that rbwr is possible */ + assert(m_read_before_write_removal_possible); + m_read_before_write_removal_used= true; + + /* Can't use on table with hidden primary key */ + assert(table_share->primary_key != MAX_KEY); + + /* Index must be unique */ + DBUG_PRINT("info", ("using index %d", active_index)); + const KEY *key= table->key_info + active_index; + assert((key->flags & HA_NOSAME)); + DBUG_VOID_RETURN; } === modified file 'sql/ha_ndbcluster.h' --- a/sql/ha_ndbcluster.h 2011-06-07 13:47:21 +0000 +++ b/sql/ha_ndbcluster.h 2011-06-09 12:14:22 +0000 @@ -188,9 +188,6 @@ class ha_ndbcluster: public handler ha_ndbcluster(handlerton *hton, TABLE_SHARE *table); ~ha_ndbcluster(); -#ifndef NDB_WITHOUT_READ_BEFORE_WRITE_REMOVAL - void column_bitmaps_signal(uint sig_type); -#endif int open(const char *name, int mode, uint test_if_locked); int close(void); void local_close(THD *thd, bool release_metadata); @@ -248,6 +245,7 @@ class ha_ndbcluster: public handler #endif void get_dynamic_partition_info(PARTITION_STATS *stat_info, uint part_id); uint32 calculate_key_hash_value(Field **field_array); + bool read_before_write_removal_supported() const { return true; } bool read_before_write_removal_possible(); ha_rows read_before_write_removal_rows_written(void) const; int extra(enum ha_extra_function operation); @@ -637,7 +635,6 @@ private: int m_current_range_no; MY_BITMAP **m_key_fields; - MY_BITMAP m_save_read_set; // NdbRecAttr has no reference to blob NdbValue m_value[NDB_MAX_ATTRIBUTES_IN_TABLE]; Uint64 m_ref; === modified file 'sql/ha_ndbcluster_glue.h' --- a/sql/ha_ndbcluster_glue.h 2011-03-08 15:17:16 +0000 +++ b/sql/ha_ndbcluster_glue.h 2011-06-09 12:14:22 +0000 @@ -67,9 +67,6 @@ bool close_cached_tables(THD *thd, TABLE /* Online alter table not supported */ #define NDB_WITHOUT_ONLINE_ALTER -/* Read before write removal not supported */ -#define NDB_WITHOUT_READ_BEFORE_WRITE_REMOVAL - /* thd has no version field anymore */ #define NDB_THD_HAS_NO_VERSION === modified file 'sql/handler.h' --- a/sql/handler.h 2011-05-12 08:43:50 +0000 +++ b/sql/handler.h 2011-06-09 12:14:22 +0000 @@ -1591,6 +1591,30 @@ public: virtual int extra_opt(enum ha_extra_function operation, ulong cache_size) { return extra(operation); } +#ifndef MCP_WL5906 + /* + Informs the handler if this handler support read removal + (could use table_flags, but patch is smaller this way) + */ + virtual bool read_before_write_removal_supported(void) const + { return false; } + + /* + Informs handler that it is possible to optimise away the real read + operation from the handler for the current table and instead + use a generated read to optimise simple UPDATE and DELETEs. + */ + virtual bool read_before_write_removal_possible(void) + { return false; } + + /* + Return the number of rows the handler has written while using + read before write removal + */ + virtual ha_rows read_before_write_removal_rows_written(void) const + { DBUG_ASSERT(0); return (ha_rows) 0; } +#endif + /** In an UPDATE or DELETE, if the row under the cursor was locked by another transaction, and the engine used an optimistic read of the last === modified file 'sql/sql_delete.cc' --- a/sql/sql_delete.cc 2010-11-16 12:37:26 +0000 +++ b/sql/sql_delete.cc 2011-06-09 12:14:22 +0000 @@ -59,6 +59,9 @@ bool mysql_delete(THD *thd, TABLE_LIST * bool const_cond_result; ha_rows deleted= 0; bool reverse= FALSE; +#ifndef MCP_WL5906 + bool read_removal= false; +#endif bool skip_record; ORDER *order= (ORDER *) ((order_list && order_list->elements) ? order_list->first : NULL); @@ -291,6 +294,33 @@ bool mysql_delete(THD *thd, TABLE_LIST * else will_batch= !table->file->start_bulk_delete(); +#ifndef MCP_WL5906 + /* + Read removal is possible if the selected quick read + method is using full unique index + */ + if (select && select->quick && + will_batch && + !using_limit && + table->file->read_before_write_removal_supported()) + { + const uint idx = select->quick->index; + DBUG_PRINT("rbwr", ("checking index: %d", idx)); + const KEY *key= table->key_info + idx; + if ((key->flags & HA_NOSAME) == HA_NOSAME) + { + DBUG_PRINT("rbwr", ("index is unique")); + bitmap_clear_all(&table->tmp_set); + table->mark_columns_used_by_index_no_reset(idx, &table->tmp_set); + if (bitmap_cmp(&table->tmp_set, table->read_set)) + { + DBUG_PRINT("rbwr", ("using whole index, rbwr possible")); + read_removal= + table->file->read_before_write_removal_possible(); + } + } + } +#endif table->mark_columns_needed_for_delete(); @@ -353,6 +383,15 @@ bool mysql_delete(THD *thd, TABLE_LIST * table->file->print_error(loc_error,MYF(0)); error=1; } +#ifndef MCP_WL5906 + if (read_removal) + { + /* Only handler knows how many records really was written */ + DBUG_PRINT("rbwr", ("old deleted: %ld", (long)deleted)); + deleted= table->file->read_before_write_removal_rows_written(); + DBUG_PRINT("rbwr", ("really deleted: %ld", (long)deleted)); + } +#endif thd_proc_info(thd, "end"); end_read_record(&info); if (options & OPTION_QUICK) === modified file 'sql/sql_update.cc' --- a/sql/sql_update.cc 2011-05-10 09:48:14 +0000 +++ b/sql/sql_update.cc 2011-06-09 12:14:22 +0000 @@ -158,6 +158,39 @@ static bool check_fields(THD *thd, List< } +#ifndef MCP_WL5906 +/* + Check if all expressions in list are constant expressions + + SYNOPSIS + check_constant_expressions() + values List of expressions + + RETURN + TRUE Only constant expressions + FALSE At least one non-constant expression +*/ + +static bool check_constant_expressions(List &values) +{ + Item *value; + List_iterator_fast v(values); + DBUG_ENTER("check_constant_expressions"); + + while ((value= v++)) + { + if (!value->const_item()) + { + DBUG_PRINT("exit", ("expression is not constant")); + DBUG_RETURN(FALSE); + } + } + DBUG_PRINT("exit", ("expression is constant")); + DBUG_RETURN(TRUE); +} +#endif + + /** Re-read record if more columns are needed for error message. @@ -422,6 +455,38 @@ int mysql_update(THD *thd, DBUG_RETURN(0); } +#ifndef MCP_WL5906 + /* + Read removal is possible if the selected quick read + method is using full unique index + + NOTE! table->read_set currently holds the columns which are + used for the WHERE clause(this info is most likely already + available in select->quick, but where?) + */ + bool read_removal= false; + if (select && select->quick && + !ignore && + !using_limit && + table->file->read_before_write_removal_supported()) + { + const uint idx= select->quick->index; + DBUG_PRINT("rbwr", ("checking index: %d", idx)); + const KEY *key= table->key_info + idx; + if ((key->flags & HA_NOSAME) == HA_NOSAME) + { + DBUG_PRINT("rbwr", ("index is unique")); + bitmap_clear_all(&table->tmp_set); + table->mark_columns_used_by_index_no_reset(idx, &table->tmp_set); + if (bitmap_cmp(&table->tmp_set, table->read_set)) + { + DBUG_PRINT("rbwr", ("using full index, rbwr possible")); + read_removal= true; + } + } + } +#endif + /* If running in safe sql mode, don't allow updates without keys */ if (table->quick_keys.is_clear_all()) { @@ -595,6 +660,13 @@ int mysql_update(THD *thd, } if (table->key_read) table->restore_column_maps_after_mark_index(); + +#ifndef MCP_WL5906 + /* Rows are already read -> not possible to remove */ + DBUG_PRINT("rbwr", ("rows are already read, turning off rbwr")); + read_removal= false; +#endif + } if (ignore) @@ -634,6 +706,16 @@ int mysql_update(THD *thd, else will_batch= !table->file->start_bulk_update(); +#ifndef MCP_WL5906 + if (read_removal && + will_batch && + check_constant_expressions(values)) + { + assert(select && select->quick); + read_removal= table->file->read_before_write_removal_possible(); + } +#endif + /* Assure that we can use position() if we need to create an error message. @@ -840,6 +922,22 @@ int mysql_update(THD *thd, table->file->end_bulk_update(); table->file->try_semi_consistent_read(0); +#ifndef MCP_WL5906 + if (read_removal) + { + /* Only handler knows how many records really was written */ + DBUG_PRINT("rbwr", ("adjusting updated: %ld, found: %ld", + (long)updated, (long)found)); + + updated= table->file->read_before_write_removal_rows_written(); + if (!records_are_comparable(table)) + found= updated; + + DBUG_PRINT("rbwr", ("really updated: %ld, found: %ld", + (long)updated, (long)found)); + } +#endif + if (!transactional_table && updated > 0) thd->transaction.stmt.modified_non_trans_table= TRUE; --===============8211551407043011866== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/magnus.blaudd@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: magnus.blaudd@stripped\ # y0ws0vfggwflo34m # target_branch: file:///data0/magnus/mysql/5.5-cluster-rbwr/ # testament_sha1: 5d82040dd1d4e1e23807cd2512c704dd0b1e9e6b # timestamp: 2011-06-09 14:18:17 +0200 # base_revision_id: magnus.blaudd@stripped\ # 5y5txpcep4jkx6p6 # # Begin bundle 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 --===============8211551407043011866==--