Skip to content

Commit 11cac91

Browse files
author
Steinar H. Gunderson
committed
Bug #29699759: ORDER BY WITH DISTINCT CAUSES OUT-OF-BOUNDS READ
When having two sorts on the same table with no temporary in-between (e.g. first for DISTINCT, then for ORDER BY), they would both try to store their buffers in table->sort. Fix so that SortingIterator owns the buffers, so that the two sorts have separate results; it doesn't really belong in TABLE anyway. Change-Id: Ide5bfccc81e13dc657ef6c5f3ab36c3b2f5009c1
1 parent 068b2a2 commit 11cac91

9 files changed

+119
-81
lines changed

sql/filesort.cc

+34-30
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
#include "sql/psi_memory_key.h"
9595
#include "sql/row_iterator.h"
9696
#include "sql/sort_param.h"
97+
#include "sql/sorting_iterator.h"
9798
#include "sql/sql_array.h"
9899
#include "sql/sql_base.h"
99100
#include "sql/sql_bitmap.h"
@@ -347,12 +348,13 @@ static void trace_filesort_information(Opt_trace_context *trace,
347348
in sorted order. This should be done with the functions
348349
in records.cc.
349350
350-
The result set is stored in table->sort.io_cache or
351-
table->sort.sorted_result, or left in the main filesort buffer.
351+
The result set is stored in fs_info->io_cache or
352+
fs_info->sorted_result, or left in the main filesort buffer.
352353
353354
@param thd Current thread
354355
@param filesort How to sort the table
355356
@param source_iterator Where to read the rows to be sorted from.
357+
@param fs_info Owns the buffers for sort_result.
356358
@param sort_result Where to store the sort result.
357359
@param[out] found_rows Store the number of found rows here.
358360
This is the number of found rows after
@@ -366,7 +368,8 @@ static void trace_filesort_information(Opt_trace_context *trace,
366368
*/
367369

368370
bool filesort(THD *thd, Filesort *filesort, RowIterator *source_iterator,
369-
Sort_result *sort_result, ha_rows *found_rows) {
371+
Filesort_info *fs_info, Sort_result *sort_result,
372+
ha_rows *found_rows) {
370373
int error;
371374
ulong memory_available = thd->variables.sortbuff_size;
372375
ha_rows num_rows_found = HA_POS_ERROR;
@@ -424,7 +427,7 @@ bool filesort(THD *thd, Filesort *filesort, RowIterator *source_iterator,
424427
table, thd->variables.max_length_for_sort_data,
425428
max_rows, filesort->m_remove_duplicates);
426429

427-
table->sort.addon_fields = param->addon_fields;
430+
fs_info->addon_fields = param->addon_fields;
428431

429432
/*
430433
TODO: Now that we read from RowIterators, the situation is a lot more
@@ -457,7 +460,7 @@ bool filesort(THD *thd, Filesort *filesort, RowIterator *source_iterator,
457460
// However, do note this cannot change the addon fields status,
458461
// so that we at least know that when checking whether we can skip
459462
// in-between temporary tables (StreamingIterator).
460-
if (check_if_pq_applicable(trace, param, &table->sort, num_rows_estimate,
463+
if (check_if_pq_applicable(trace, param, fs_info, num_rows_estimate,
461464
memory_available)) {
462465
DBUG_PRINT("info", ("filesort PQ is applicable"));
463466
/*
@@ -466,19 +469,19 @@ bool filesort(THD *thd, Filesort *filesort, RowIterator *source_iterator,
466469
all pointers here. (We cannot pack fields anyways, so there is no
467470
point in doing incremental allocation).
468471
*/
469-
if (table->sort.preallocate_records(param->max_rows_per_buffer)) {
472+
if (fs_info->preallocate_records(param->max_rows_per_buffer)) {
470473
my_error(ER_OUT_OF_SORTMEMORY, ME_FATALERROR);
471474
LogErr(ERROR_LEVEL, ER_SERVER_OUT_OF_SORTMEMORY);
472475
goto err;
473476
}
474477

475-
if (pq.init(param->max_rows, param, table->sort.get_sort_keys())) {
478+
if (pq.init(param->max_rows, param, fs_info->get_sort_keys())) {
476479
/*
477480
If we fail to init pq, we have to give up:
478481
out of memory means my_malloc() will call my_error().
479482
*/
480483
DBUG_PRINT("info", ("failed to allocate PQ"));
481-
table->sort.free_sort_buffer();
484+
fs_info->free_sort_buffer();
482485
DBUG_ASSERT(thd->is_error());
483486
goto err;
484487
}
@@ -506,7 +509,7 @@ bool filesort(THD *thd, Filesort *filesort, RowIterator *source_iterator,
506509
param->max_rows_per_buffer =
507510
min(num_rows_estimate > 0 ? num_rows_estimate : 1, keys);
508511

509-
table->sort.set_max_size(memory_available, param->max_record_length());
512+
fs_info->set_max_size(memory_available, param->max_record_length());
510513
}
511514

512515
param->sort_form = table;
@@ -515,10 +518,9 @@ bool filesort(THD *thd, Filesort *filesort, RowIterator *source_iterator,
515518
// New scope, because subquery execution must be traced within an array.
516519
{
517520
Opt_trace_array ota(trace, "filesort_execution");
518-
num_rows_found =
519-
read_all_rows(thd, param, qep_tab, &table->sort, &chunk_file, &tempfile,
520-
param->using_pq ? &pq : nullptr, source_iterator,
521-
found_rows, &longest_key);
521+
num_rows_found = read_all_rows(thd, param, qep_tab, fs_info, &chunk_file,
522+
&tempfile, param->using_pq ? &pq : nullptr,
523+
source_iterator, found_rows, &longest_key);
522524
if (num_rows_found == HA_POS_ERROR) goto err;
523525
}
524526

@@ -536,7 +538,7 @@ bool filesort(THD *thd, Filesort *filesort, RowIterator *source_iterator,
536538
{
537539
ha_rows rows_in_chunk =
538540
param->using_pq ? pq.num_elements() : num_rows_found;
539-
if (save_index(param, rows_in_chunk, &table->sort, sort_result)) goto err;
541+
if (save_index(param, rows_in_chunk, fs_info, sort_result)) goto err;
540542
} else {
541543
// If deduplicating, we'll need to remember the previous key somehow.
542544
if (filesort->m_remove_duplicates) {
@@ -545,12 +547,12 @@ bool filesort(THD *thd, Filesort *filesort, RowIterator *source_iterator,
545547
}
546548

547549
// We will need an extra buffer in SortFileIndirectIterator
548-
if (table->sort.addon_fields != nullptr &&
549-
!(table->sort.addon_fields->allocate_addon_buf(param->m_addon_length)))
550+
if (fs_info->addon_fields != nullptr &&
551+
!(fs_info->addon_fields->allocate_addon_buf(param->m_addon_length)))
550552
goto err; /* purecov: inspected */
551553

552-
table->sort.read_chunk_descriptors(&chunk_file, num_chunks);
553-
if (table->sort.merge_chunks.is_null()) goto err; /* purecov: inspected */
554+
fs_info->read_chunk_descriptors(&chunk_file, num_chunks);
555+
if (fs_info->merge_chunks.is_null()) goto err; /* purecov: inspected */
554556

555557
close_cached_file(&chunk_file);
556558

@@ -562,23 +564,23 @@ bool filesort(THD *thd, Filesort *filesort, RowIterator *source_iterator,
562564
if (reinit_io_cache(outfile, WRITE_CACHE, 0L, 0, 0)) goto err;
563565

564566
param->max_rows_per_buffer = static_cast<uint>(
565-
table->sort.max_size_in_bytes() / param->max_record_length());
567+
fs_info->max_size_in_bytes() / param->max_record_length());
566568

567-
Bounds_checked_array<uchar> merge_buf = table->sort.get_contiguous_buffer();
569+
Bounds_checked_array<uchar> merge_buf = fs_info->get_contiguous_buffer();
568570
if (merge_buf.array() == nullptr) {
569571
my_error(ER_OUT_OF_SORTMEMORY, ME_FATALERROR);
570572
LogErr(ERROR_LEVEL, ER_SERVER_OUT_OF_SORTMEMORY);
571573
goto err;
572574
}
573-
if (merge_many_buff(thd, param, merge_buf, table->sort.merge_chunks,
575+
if (merge_many_buff(thd, param, merge_buf, fs_info->merge_chunks,
574576
&num_chunks, &tempfile))
575577
goto err;
576578
if (flush_io_cache(&tempfile) ||
577579
reinit_io_cache(&tempfile, READ_CACHE, 0L, 0, 0))
578580
goto err;
579581
if (merge_index(
580582
thd, param, merge_buf,
581-
Merge_chunk_array(table->sort.merge_chunks.begin(), num_chunks),
583+
Merge_chunk_array(fs_info->merge_chunks.begin(), num_chunks),
582584
&tempfile, outfile))
583585
goto err;
584586

@@ -611,7 +613,7 @@ bool filesort(THD *thd, Filesort *filesort, RowIterator *source_iterator,
611613
.add("num_rows_estimate", num_rows_estimate)
612614
.add("num_rows_found", num_rows_found)
613615
.add("num_initial_chunks_spilled_to_disk", num_initial_chunks)
614-
.add("peak_memory_used", table->sort.peak_memory_used())
616+
.add("peak_memory_used", fs_info->peak_memory_used())
615617
.add_alnum("sort_algorithm", algo_text[param->m_sort_algorithm]);
616618
if (!param->using_packed_addons())
617619
filesort_summary.add_alnum(
@@ -628,9 +630,9 @@ bool filesort(THD *thd, Filesort *filesort, RowIterator *source_iterator,
628630

629631
err:
630632
if (!subselect || !subselect->is_uncacheable()) {
631-
if (!sort_result->sorted_result_in_fsbuf) table->sort.free_sort_buffer();
632-
my_free(table->sort.merge_chunks.array());
633-
table->sort.merge_chunks = Merge_chunk_array(NULL, 0);
633+
if (!sort_result->sorted_result_in_fsbuf) fs_info->free_sort_buffer();
634+
my_free(fs_info->merge_chunks.array());
635+
fs_info->merge_chunks = Merge_chunk_array(NULL, 0);
634636
}
635637
close_cached_file(&tempfile);
636638
close_cached_file(&chunk_file);
@@ -689,10 +691,12 @@ void filesort_free_buffers(TABLE *table, bool full) {
689691
table->unique_result.sorted_result_in_fsbuf = false;
690692

691693
if (full) {
692-
table->sort.free_sort_buffer();
693-
my_free(table->sort.merge_chunks.array());
694-
table->sort.merge_chunks = Merge_chunk_array(NULL, 0);
695-
table->sort.addon_fields = NULL;
694+
if (table->sorting_iterator != nullptr) {
695+
table->sorting_iterator->CleanupAfterQuery();
696+
}
697+
if (table->duplicate_removal_iterator != nullptr) {
698+
table->duplicate_removal_iterator->CleanupAfterQuery();
699+
}
696700
}
697701
}
698702

sql/filesort.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ class Filesort {
9393
};
9494

9595
bool filesort(THD *thd, Filesort *fsort, RowIterator *source_iterator,
96-
Sort_result *sort_result, ha_rows *found_rows);
96+
Filesort_info *fs_info, Sort_result *sort_result,
97+
ha_rows *found_rows);
9798
void filesort_free_buffers(TABLE *table, bool full);
9899
void change_double_for_sort(double nr, uchar *to);
99100

sql/filesort_utils.h

+4-26
Original file line numberDiff line numberDiff line change
@@ -39,31 +39,6 @@
3939
class Cost_model_table;
4040
class Sort_param;
4141

42-
/*
43-
Calculate cost of merge sort
44-
45-
@param num_rows Total number of rows.
46-
@param num_keys_per_buffer Number of keys per buffer.
47-
@param elem_size Size of each element.
48-
@param cost_model Cost model object that provides cost data.
49-
50-
Calculates cost of merge sort by simulating call to merge_many_buff().
51-
52-
@returns
53-
Computed cost of merge sort in disk seeks.
54-
55-
@note
56-
Declared here in order to be able to unit test it,
57-
since library dependencies have not been sorted out yet.
58-
59-
See also comments get_merge_many_buffs_cost().
60-
*/
61-
62-
double get_merge_many_buffs_cost_fast(ha_rows num_rows,
63-
ha_rows num_keys_per_buffer,
64-
uint elem_size,
65-
const Cost_model_table *cost_model);
66-
6742
/**
6843
Buffer used for storing records to be sorted. The records are stored in
6944
a series of buffers that are allocated incrementally, growing 50% each
@@ -218,7 +193,10 @@ class Filesort_buffer {
218193
Gets sorted record number ix. @see get_sort_keys()
219194
Only valid after buffer has been sorted!
220195
*/
221-
uchar *get_sorted_record(size_t ix) { return m_record_pointers[ix]; }
196+
uchar *get_sorted_record(size_t ix) {
197+
DBUG_ASSERT(ix < m_record_pointers.size());
198+
return m_record_pointers[ix];
199+
}
222200

223201
/**
224202
Clears all rows, then returns a contiguous buffer of maximum size.

sql/sorting_iterator.cc

+32-21
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,7 @@ bool SortFileIndirectIterator::Init() {
9898
return true;
9999
}
100100

101-
/*
102-
table->sort.addon_field is checked because if we use addon fields,
103-
it doesn't make sense to use cache - we don't read from the table
104-
and table->sort.io_cache is read sequentially
105-
*/
106-
if (m_using_cache && !table()->sort.using_addon_fields() &&
107-
thd()->variables.read_rnd_buff_size &&
101+
if (m_using_cache && thd()->variables.read_rnd_buff_size &&
108102
!(table()->file->ha_table_flags() & HA_FAST_KEY_READ) &&
109103
(table()->db_stat & HA_READ_ONLY ||
110104
table()->reginfo.lock_type <= TL_READ_NO_INSERT) &&
@@ -468,7 +462,17 @@ SortingIterator::SortingIterator(THD *thd, Filesort *filesort,
468462
m_source_iterator(move(source)),
469463
m_examined_rows(examined_rows) {}
470464

471-
SortingIterator::~SortingIterator() { ReleaseBuffers(); }
465+
SortingIterator::~SortingIterator() {
466+
ReleaseBuffers();
467+
CleanupAfterQuery();
468+
}
469+
470+
void SortingIterator::CleanupAfterQuery() {
471+
m_fs_info.free_sort_buffer();
472+
my_free(m_fs_info.merge_chunks.array());
473+
m_fs_info.merge_chunks = Merge_chunk_array(NULL, 0);
474+
m_fs_info.addon_fields = NULL;
475+
}
472476

473477
void SortingIterator::ReleaseBuffers() {
474478
m_result_iterator.reset();
@@ -480,6 +484,8 @@ void SortingIterator::ReleaseBuffers() {
480484
}
481485
m_sort_result.sorted_result.reset();
482486
m_sort_result.sorted_result_in_fsbuf = false;
487+
488+
// Keep the sort buffer in m_fs_info.
483489
}
484490

485491
bool SortingIterator::Init() {
@@ -522,42 +528,47 @@ bool SortingIterator::Init() {
522528
TABLE *table = qep_tab->table();
523529
if (m_sort_result.io_cache && my_b_inited(m_sort_result.io_cache)) {
524530
// Test if ref-records was used
525-
if (table->sort.using_addon_fields()) {
531+
if (m_fs_info.using_addon_fields()) {
526532
DBUG_PRINT("info", ("using SortFileIterator"));
527-
if (table->sort.addon_fields->using_packed_addons())
533+
if (m_fs_info.addon_fields->using_packed_addons())
528534
m_result_iterator.reset(
529535
new (&m_result_iterator_holder.sort_file_packed_addons)
530536
SortFileIterator<true>(thd(), table, m_sort_result.io_cache,
531-
&table->sort, m_examined_rows));
537+
&m_fs_info, m_examined_rows));
532538
else
533539
m_result_iterator.reset(
534540
new (&m_result_iterator_holder.sort_file)
535541
SortFileIterator<false>(thd(), table, m_sort_result.io_cache,
536-
&table->sort, m_examined_rows));
542+
&m_fs_info, m_examined_rows));
537543
} else {
544+
/*
545+
m_fs_info->addon_field is checked because if we use addon fields,
546+
it doesn't make sense to use cache - we don't read from the table
547+
and m_fs_info->io_cache is read sequentially
548+
*/
549+
bool request_cache = !m_fs_info.using_addon_fields();
538550
m_result_iterator.reset(
539551
new (&m_result_iterator_holder.sort_file_indirect)
540-
SortFileIndirectIterator(thd(), table, m_sort_result.io_cache,
541-
/*request_cache=*/true,
542-
/*ignore_not_found_rows=*/false,
543-
m_examined_rows));
552+
SortFileIndirectIterator(
553+
thd(), table, m_sort_result.io_cache, request_cache,
554+
/*ignore_not_found_rows=*/false, m_examined_rows));
544555
}
545556
m_sort_result.io_cache =
546557
nullptr; // The result iterator has taken ownership.
547558
} else {
548559
DBUG_ASSERT(m_sort_result.has_result_in_memory());
549-
if (table->sort.using_addon_fields()) {
560+
if (m_fs_info.using_addon_fields()) {
550561
DBUG_PRINT("info", ("using SortBufferIterator"));
551562
DBUG_ASSERT(m_sort_result.sorted_result_in_fsbuf);
552-
if (table->sort.addon_fields->using_packed_addons())
563+
if (m_fs_info.addon_fields->using_packed_addons())
553564
m_result_iterator.reset(
554565
new (&m_result_iterator_holder.sort_buffer_packed_addons)
555-
SortBufferIterator<true>(thd(), table, &table->sort,
566+
SortBufferIterator<true>(thd(), table, &m_fs_info,
556567
&m_sort_result, m_examined_rows));
557568
else
558569
m_result_iterator.reset(
559570
new (&m_result_iterator_holder.sort_buffer)
560-
SortBufferIterator<false>(thd(), table, &table->sort,
571+
SortBufferIterator<false>(thd(), table, &m_fs_info,
561572
&m_sort_result, m_examined_rows));
562573
} else {
563574
DBUG_PRINT("info", ("using SortBufferIndirectIterator (sort)"));
@@ -621,7 +632,7 @@ int SortingIterator::DoSort(QEP_TAB *qep_tab) {
621632
}
622633

623634
ha_rows found_rows;
624-
bool error = filesort(thd(), m_filesort, m_source_iterator.get(),
635+
bool error = filesort(thd(), m_filesort, m_source_iterator.get(), &m_fs_info,
625636
&m_sort_result, &found_rows);
626637
qep_tab->set_records(found_rows); // For SQL_CALC_ROWS
627638
table->set_keyread(false); // Restore if we used indexes

sql/sorting_iterator.h

+12
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,15 @@ class SortingIterator final : public RowIterator {
9191

9292
std::vector<std::string> DebugString() const override;
9393

94+
/// Optional (when JOIN::destroy() runs, the iterator and its buffers
95+
/// will be cleaned up anyway); used to clean up the buffers a little
96+
/// bit earlier.
97+
///
98+
/// When we get cached JOIN objects (prepare/optimize once) that can
99+
/// live for a long time between queries, calling this will become more
100+
/// important.
101+
void CleanupAfterQuery();
102+
94103
private:
95104
int DoSort(QEP_TAB *qep_tab);
96105
void ReleaseBuffers();
@@ -109,6 +118,9 @@ class SortingIterator final : public RowIterator {
109118
// using packed addons, etc..
110119
unique_ptr_destroy_only<RowIterator> m_result_iterator;
111120

121+
// Holds the buffers for m_sort_result.
122+
Filesort_info m_fs_info;
123+
112124
Sort_result m_sort_result;
113125

114126
ha_rows *m_examined_rows;

0 commit comments

Comments
 (0)