diff --git a/include/my_bitmap.h b/include/my_bitmap.h index 05bec8012e0..77052876eb6 100644 --- a/include/my_bitmap.h +++ b/include/my_bitmap.h @@ -57,6 +57,7 @@ extern uint bitmap_set_next(MY_BITMAP *map); extern uint bitmap_get_first(const MY_BITMAP *map); extern uint bitmap_get_first_set(const MY_BITMAP *map); extern uint bitmap_get_next_set(const MY_BITMAP *map, uint bitmap_bit); +extern uint bitmap_get_last_set(const MY_BITMAP *map); extern uint bitmap_bits_set(const MY_BITMAP *map); extern void bitmap_free(MY_BITMAP *map); extern void bitmap_set_above(MY_BITMAP *map, uint from_byte, bool use_bit); diff --git a/mysys/my_bitmap.cc b/mysys/my_bitmap.cc index 62ae04ea886..ff2cc45bec2 100644 --- a/mysys/my_bitmap.cc +++ b/mysys/my_bitmap.cc @@ -136,6 +136,24 @@ static inline uint get_first_not_set(uint32 value, uint word_pos) { return MY_BIT_NONE; } +static inline uint get_last_set(uint32 value, uint word_pos) { + uchar *byte_ptr = (uchar *)&value + 3; + uchar byte_value; + uint byte_pos, bit_pos; + int i, j; + + // byte_pos is unsigned int so we need i to handle end of loop + for (byte_pos = 3, i = byte_pos; i >= 0; byte_pos--, byte_ptr--, i--) { + byte_value = *byte_ptr; + if (byte_value) { + for (bit_pos = 7, j = bit_pos; j >= 0; bit_pos--, j--) + if (byte_value & (1 << bit_pos)) + return (word_pos * 32) + (byte_pos * 8) + bit_pos; + } + } + return MY_BIT_NONE; +} + bool bitmap_init(MY_BITMAP *map, my_bitmap_map *buf, uint n_bits) { DBUG_TRACE; if (!buf) { @@ -487,3 +505,22 @@ uint bitmap_get_first(const MY_BITMAP *map) { return get_first_not_set(*map->last_word_ptr | map->last_word_mask, word_pos); } + +uint bitmap_get_last_set(const MY_BITMAP *map) { + uint word_pos; + int i; + my_bitmap_map *data_ptr, *end = map->last_word_ptr, last_word; + + assert(map->bitmap); + assert(map->n_bits > 0); + word_pos = (map->n_bits - 1) / 32; + + last_word = *end & ~map->last_word_mask; + if (last_word) return get_last_set(last_word, word_pos); + + for (i = word_pos - 1, data_ptr = end - 1; i >= 0; + data_ptr--, word_pos--, i--) + if (*data_ptr) return get_last_set(*data_ptr, i); + + return MY_BIT_NONE; +} diff --git a/sql/partition_info.h b/sql/partition_info.h index 3075a57a4ab..1f13140c7df 100644 --- a/sql/partition_info.h +++ b/sql/partition_info.h @@ -540,6 +540,9 @@ class partition_info { inline uint get_first_used_partition() const { return bitmap_get_first_set(&read_partitions); } + inline uint get_last_used_partition() const { + return bitmap_get_last_set(&read_partitions); + } inline uint get_next_used_partition(uint part_id) const { return bitmap_get_next_set(&read_partitions, part_id); } diff --git a/sql/partitioning/partition_handler.cc b/sql/partitioning/partition_handler.cc index 041a94cf241..25366f392ae 100644 --- a/sql/partitioning/partition_handler.cc +++ b/sql/partitioning/partition_handler.cc @@ -1866,7 +1866,10 @@ int Partition_helper::common_index_read(uchar *buf, bool have_start_key) { m_start_key.flag == HA_READ_PREFIX_LAST_OR_PREV || m_start_key.flag == HA_READ_BEFORE_KEY)) { m_reverse_order = true; - m_ordered_scan_ongoing = true; + // Consider single partition optimization + if (m_part_info->num_partitions_used() > 1) { + m_ordered_scan_ongoing = true; + } } DBUG_PRINT("info", ("m_ordered %u m_o_scan_ong %u have_start_key %u", m_ordered, m_ordered_scan_ongoing, have_start_key)); @@ -1959,7 +1962,13 @@ int Partition_helper::common_first_last(uchar *buf) { if ((error = partition_scan_set_up(buf, false))) { return error; } - if (!m_ordered_scan_ongoing && m_index_scan_type != PARTITION_INDEX_LAST) { + + // There are two situations which changing from ordered scan to unordered scan + // in order to avoid merge sort between partitions if only single partition: + // 1. Normal ordered index scan + // 2. Group min max force to use order scan to get max/min value. + if ((!m_ordered_scan_ongoing && m_index_scan_type != PARTITION_INDEX_LAST) || + m_part_info->num_partitions_used() <= 1) { return handle_unordered_scan_next_partition(buf); } return handle_ordered_index_scan(buf); @@ -2129,9 +2138,13 @@ int Partition_helper::ph_index_next_same(uchar *buf, int Partition_helper::ph_index_prev(uchar *buf) { DBUG_TRACE; + m_index_scan_type = PARTITION_INDEX_PREV; /* TODO: read comment in index_next */ assert(m_index_scan_type != PARTITION_INDEX_FIRST || m_table->open_by_handler); + if (!m_ordered_scan_ongoing) { + return handle_unordered_prev(buf); + } return handle_ordered_prev(buf); } @@ -2227,10 +2240,9 @@ int Partition_helper::partition_scan_set_up(uchar *buf, bool idx_read_flag) { get_partition_set(m_table, buf, m_handler->active_index, &m_start_key, &m_part_spec); else { - // TODO: set to get_first_used_part() instead! - m_part_spec.start_part = 0; - // TODO: Implement bitmap_get_last_set() and use that here! - m_part_spec.end_part = m_tot_parts - 1; + // Only consider the pruned partitions. + m_part_spec.start_part = m_part_info->get_first_used_partition(); + m_part_spec.end_part = m_part_info->get_last_used_partition(); } if (m_part_spec.start_part > m_part_spec.end_part) { /* @@ -2248,6 +2260,10 @@ int Partition_helper::partition_scan_set_up(uchar *buf, bool idx_read_flag) { DBUG_PRINT("info", ("index scan using the single partition %d", m_part_spec.start_part)); m_ordered_scan_ongoing = false; + // No need to use merge sort between partitions if single parittion, so + // release the priority queue buffer memory to avoid assert failure in + // unordered scan functions. + if (m_ordered_rec_buffer) destroy_record_priority_queue(); } else { /* Set m_ordered_scan_ongoing according how the scan should be done @@ -2323,6 +2339,44 @@ int Partition_helper::handle_unordered_next(uchar *buf, bool is_next_same) { return error; } +/** + Common routine to handle index_prev with unordered results. + + These routines are used to scan partitions without considering order. + This is performed in one situation: + When performing index_prev where all fields in the partition function is + bound. In this case the index scan is performed on only one partition and + thus it isn't necessary to perform any sort. + + @param[out] buf Read row in MySQL Row Format. + + @return Operation status. + @retval HA_ERR_END_OF_FILE End of scan + @retval 0 Success + @retval other Error code +*/ +int Partition_helper::handle_unordered_prev(uchar *buf) { + int error; + DBUG_TRACE; + + if (m_part_spec.start_part >= m_tot_parts) { + /* Should only happen with SQL HANDLER! */ + assert(m_table->open_by_handler); + return HA_ERR_END_OF_FILE; + } + + error = index_prev_in_part(m_part_spec.start_part, buf); + + if (error == HA_ERR_END_OF_FILE) { + m_part_spec.start_part++; // Start using next part + error = handle_unordered_scan_next_partition(buf); + } else { + m_last_part = m_part_spec.start_part; + } + return error; +} + + /** Handle index_next when changing to new partition. @@ -2382,6 +2436,17 @@ int Partition_helper::handle_unordered_scan_next_partition(uchar *buf) { error = read_range_first_in_part(i, nullptr, nullptr, m_handler->end_range, false); break; + case PARTITION_INDEX_LAST: + /* When GROUP MIN MAX with only one partition */ + DBUG_PRINT("info", ("index_last on partition %d", i)); + assert(m_part_info->num_partitions_used() <= 1); + error = index_last_in_part(i, buf); + break; + case PARTITION_INDEX_PREV: + /* When index scan in descending order */ + DBUG_PRINT("info", ("index_prev on partition %d", i)); + error = index_prev_in_part(i, buf); + break; default: assert(0); return HA_ERR_INTERNAL_ERROR; diff --git a/sql/partitioning/partition_handler.h b/sql/partitioning/partition_handler.h index 493f29f0c0a..ffac8779786 100644 --- a/sql/partitioning/partition_handler.h +++ b/sql/partitioning/partition_handler.h @@ -752,6 +752,7 @@ class Partition_helper { PARTITION_INDEX_FIRST_UNORDERED, PARTITION_INDEX_LAST, PARTITION_INDEX_READ_LAST, + PARTITION_INDEX_PREV, PARTITION_READ_RANGE, PARTITION_NO_INDEX_SCAN }; @@ -954,6 +955,23 @@ class Partition_helper { @returns other Error code */ int handle_unordered_next(uchar *buf, bool is_next_same); + /** + Common routine to handle index_prev with unordered results. + + These routines are used to scan partitions without considering order. + This is performed in one situation: + When performing index_prev where all fields in the partition function is + bound. In this case the index scan is performed on only one partition and + thus it isn't necessary to perform any sort. + + @param[out] buf Read row in MySQL Row Format. + + @return Operation status. + @retval HA_ERR_END_OF_FILE End of scan + @retval 0 Success + @retval other Error code + */ + int handle_unordered_prev(uchar *buf); /** Handle index_next when changing to new partition. diff --git a/sql/sql_bitmap.h b/sql/sql_bitmap.h index a03d75411b3..c013b0eb63c 100644 --- a/sql/sql_bitmap.h +++ b/sql/sql_bitmap.h @@ -131,6 +131,7 @@ class Bitmap { } uint bits_set() const { return bitmap_bits_set(&map); } uint get_first_set() const { return bitmap_get_first_set(&map); } + uint get_last_set() { return bitmap_get_last_set(&map); } }; template <>