Skip to content

Commit ddf6c80

Browse files
author
Jorgen Loland
committed
Bug#14578060: INNODB: WARNING: USING A PARTIAL-FIELD KEY
PREFIX IN SEARCH When UPDATE creates a duplicate out of columns defined as unique, an error is issued. The error message needs info about the column values to make this error message, and if this info is not available in the offended index a table read is done through ha_rnd_pos(). The were multiple problems in this code. 1) the error-creation code in mysql_update() called ha_rnd_pos() without calling ha_rnd_init() first. This was OK for InnoDB in MySQL 5.5 because InnoDB noticed it and called change_active_index() internally. In 5.6 InnoDB no longer automatically switches index, resulting in this bug report. 2) When doing an UPDATE, InnoDB reads all columns of the row to update. For this reason, mysql_update() does not even have to call ha_rnd_pos() to get the info. Furthermore, MyISAM always reads all rows anyway. Due to 2), the simplest bug fix would have been to not read additional columns from the table if the SE is InnoDB. However, the problem would persist for other SEs. An alternative patch would be to open another handler that calls ha_rnd_init();ha_rnd_pos();ha_rnd_end() and is then destroyed, but it makes more sense to change the contract with the handler slightly: If ha_update_row() returns duplicate key error, all columns relevant for the error message must have been read. The need for an extra ha_rnd_pos() is therefore eliminated. MyISAM and InnoDB already obeys this new contract. NDB Cluster (Magnus) has agreed to fix this in their codebase.
1 parent 90a04e3 commit ddf6c80

4 files changed

Lines changed: 19 additions & 82 deletions

File tree

sql/handler.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2990,6 +2990,14 @@ class handler :public Sql_alloc
29902990
return HA_ERR_WRONG_COMMAND;
29912991
}
29922992

2993+
/**
2994+
Update a single row.
2995+
2996+
Note: If HA_ERR_FOUND_DUPP_KEY is returned, the handler must read
2997+
all columns of the row so MySQL can create an error message. If
2998+
the columns required for the error message are not read, the error
2999+
message will contain garbage.
3000+
*/
29933001
virtual int update_row(const uchar *old_data __attribute__((unused)),
29943002
uchar *new_data __attribute__((unused)))
29953003
{
@@ -3066,12 +3074,15 @@ class handler :public Sql_alloc
30663074
that another call to bulk_update_row will occur OR a call to
30673075
exec_bulk_update before the set of updates in this query is concluded.
30683076
3077+
Note: If HA_ERR_FOUND_DUPP_KEY is returned, the handler must read
3078+
all columns of the row so MySQL can create an error message. If
3079+
the columns required for the error message are not read, the error
3080+
message will contain garbage.
3081+
30693082
@param old_data Old record
30703083
@param new_data New record
30713084
@param dup_key_found Number of duplicate keys found
30723085
3073-
@retval 0 Bulk delete used by handler
3074-
@retval 1 Bulk delete not used, normal operation used
30753086
*/
30763087
virtual int bulk_update_row(const uchar *old_data, uchar *new_data,
30773088
uint *dup_key_found)

sql/sql_update.cc

Lines changed: 0 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -190,75 +190,6 @@ static bool check_constant_expressions(List<Item> &values)
190190
}
191191

192192

193-
/**
194-
Re-read record if more columns are needed for error message.
195-
196-
If we got a duplicate key error, we want to write an error
197-
message containing the value of the duplicate key. If we do not have
198-
all fields of the key value in record[0], we need to re-read the
199-
record with a proper read_set.
200-
201-
@param[in] error error number
202-
@param[in] table table
203-
*/
204-
205-
static void prepare_record_for_error_message(int error, TABLE *table)
206-
{
207-
Field **field_p;
208-
Field *field;
209-
uint keynr;
210-
MY_BITMAP unique_map; /* Fields in offended unique. */
211-
my_bitmap_map unique_map_buf[bitmap_buffer_size(MAX_FIELDS)];
212-
DBUG_ENTER("prepare_record_for_error_message");
213-
214-
/*
215-
Only duplicate key errors print the key value.
216-
If storage engine does always read all columns, we have the value alraedy.
217-
*/
218-
if ((error != HA_ERR_FOUND_DUPP_KEY) ||
219-
!(table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ))
220-
DBUG_VOID_RETURN;
221-
222-
/*
223-
Get the number of the offended index.
224-
We will see MAX_KEY if the engine cannot determine the affected index.
225-
*/
226-
if ((keynr= table->file->get_dup_key(error)) >= MAX_KEY)
227-
DBUG_VOID_RETURN;
228-
229-
/* Create unique_map with all fields used by that index. */
230-
bitmap_init(&unique_map, unique_map_buf, table->s->fields, FALSE);
231-
table->mark_columns_used_by_index_no_reset(keynr, &unique_map);
232-
233-
/* Subtract read_set and write_set. */
234-
bitmap_subtract(&unique_map, table->read_set);
235-
bitmap_subtract(&unique_map, table->write_set);
236-
237-
/*
238-
If the unique index uses columns that are neither in read_set
239-
nor in write_set, we must re-read the record.
240-
Otherwise no need to do anything.
241-
*/
242-
if (bitmap_is_clear_all(&unique_map))
243-
DBUG_VOID_RETURN;
244-
245-
/* Get identifier of last read record into table->file->ref. */
246-
table->file->position(table->record[0]);
247-
/* Add all fields used by unique index to read_set. */
248-
bitmap_union(table->read_set, &unique_map);
249-
/* Tell the engine about the new set. */
250-
table->file->column_bitmaps_signal();
251-
/* Read record that is identified by table->file->ref. */
252-
(void) table->file->ha_rnd_pos(table->record[1], table->file->ref);
253-
/* Copy the newly read columns into the new record. */
254-
for (field_p= table->field; (field= *field_p); field_p++)
255-
if (bitmap_is_set(&unique_map, field->field_index))
256-
field->copy_from_tmp(table->s->rec_buff_length);
257-
258-
DBUG_VOID_RETURN;
259-
}
260-
261-
262193
/*
263194
Process usual UPDATE
264195
@@ -793,10 +724,6 @@ int mysql_update(THD *thd,
793724
check_constant_expressions(values))
794725
read_removal= table->check_read_removal(select->quick->index);
795726

796-
// For prepare_record_for_error_message():
797-
if (table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ)
798-
table->prepare_for_position();
799-
800727
while (!(error=info.read_record(&info)) && !thd->killed)
801728
{
802729
thd->inc_examined_row_count(1);
@@ -895,7 +822,6 @@ int mysql_update(THD *thd,
895822
if (table->file->is_fatal_error(error, HA_CHECK_DUP_KEY))
896823
flags|= ME_FATALERROR; /* Other handler errors are fatal */
897824

898-
prepare_record_for_error_message(error, table);
899825
table->file->print_error(error,MYF(flags));
900826
error= 1;
901827
break;
@@ -932,7 +858,6 @@ int mysql_update(THD *thd,
932858
The handler should not report error of duplicate keys if they
933859
are ignored. This is a requirement on batching handlers.
934860
*/
935-
prepare_record_for_error_message(error, table);
936861
table->file->print_error(error,MYF(0));
937862
error= 1;
938863
break;
@@ -993,7 +918,6 @@ int mysql_update(THD *thd,
993918
*/
994919
{
995920
/* purecov: begin inspected */
996-
prepare_record_for_error_message(loc_error, table);
997921
table->file->print_error(loc_error,MYF(ME_FATALERROR));
998922
error= 1;
999923
/* purecov: end */
@@ -2135,7 +2059,6 @@ bool multi_update::send_data(List<Item> &not_used_values)
21352059
if (table->file->is_fatal_error(error, HA_CHECK_DUP_KEY))
21362060
flags|= ME_FATALERROR; /* Other handler errors are fatal */
21372061

2138-
prepare_record_for_error_message(error, table);
21392062
table->file->print_error(error,MYF(flags));
21402063
DBUG_RETURN(1);
21412064
}
@@ -2416,7 +2339,6 @@ int multi_update::do_updates()
24162339

24172340
err:
24182341
{
2419-
prepare_record_for_error_message(local_error, table);
24202342
table->file->print_error(local_error,MYF(ME_FATALERROR));
24212343
}
24222344

sql/table.cc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4935,13 +4935,16 @@ void TABLE::clear_column_bitmaps()
49354935
}
49364936

49374937

4938-
/*
4938+
/**
49394939
Tell handler we are going to call position() and rnd_pos() later.
49404940
4941-
NOTES:
49424941
This is needed for handlers that uses the primary key to find the
49434942
row. In this case we have to extend the read bitmap with the primary
49444943
key fields.
4944+
4945+
@note: Calling this function does not initialize the table for
4946+
reading using rnd_pos(). rnd_init() still has to be called before
4947+
rnd_pos().
49454948
*/
49464949

49474950
void TABLE::prepare_for_position()

storage/innobase/row/row0sel.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2533,6 +2533,7 @@ row_sel_convert_mysql_key_to_innobase(
25332533
dfield_set_len(dfield, len
25342534
- (ulint) (key_ptr - key_end));
25352535
}
2536+
ut_ad(0);
25362537
}
25372538

25382539
n_fields++;

0 commit comments

Comments
 (0)