@@ -2792,6 +2792,149 @@ JOIN::optimize_inner()
27922792 DBUG_RETURN(0);
27932793}
27942794
2795+ enum class Parallel_scan_type
2796+ {
2797+ NONE,
2798+ FULL_SCAN,
2799+ RANGE_SCAN
2800+ };
2801+
2802+ static
2803+ Parallel_scan_type get_applicable_parallel_scan_type(JOIN_TAB *join_tab)
2804+ {
2805+ if (!(join_tab->type == JT_ALL &&
2806+ join_tab->read_first_record == join_init_read_record &&
2807+ join_tab->table->s->tmp_table == NO_TMP_TABLE &&
2808+ join_tab->table->s->blob_fields == 0 &&
2809+ !join_tab->table->fulltext_searched &&
2810+ #ifdef WITH_PARTITION_STORAGE_ENGINE
2811+ !join_tab->table->part_info &&
2812+ #endif
2813+ (join_tab->table->file->ha_table_flags() & HA_CAN_PARALLEL_SCAN)))
2814+ {
2815+ return Parallel_scan_type::NONE;
2816+ }
2817+
2818+ /*
2819+ The plan may be relying on this table's rows arriving in index order to
2820+ satisfy ORDER BY or GROUP BY without a filesort, but a parallel scan
2821+ does not preserve that order. Reject parallelization in that case.
2822+ */
2823+ if (join_tab->join->ordered_index_usage != JOIN::ordered_index_void)
2824+ return Parallel_scan_type::NONE;
2825+
2826+ if (join_tab->filesort || join_tab->filesort_result ||
2827+ join_tab->need_to_build_rowid_filter || join_tab->rowid_filter ||
2828+ join_tab->distinct ||
2829+ join_tab->table->file->keyread_enabled())
2830+ return Parallel_scan_type::NONE;
2831+
2832+ SQL_SELECT *sql_select= join_tab->select;
2833+
2834+ if (sql_select && sql_select->quick)
2835+ {
2836+ /*
2837+ The case of a range scan.
2838+ Only a plain range over the clustered index can be handed to the
2839+ parallel coordinator, and only if it has few enough intervals to be
2840+ worth splitting.
2841+ */
2842+ const uint MAX_PARALLEL_SCAN_RANGES= 128;
2843+ if (sql_select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE &&
2844+ sql_select->quick->index == join_tab->table->s->primary_key &&
2845+ join_tab->use_quick != 2 /*exclude dynamic range*/ &&
2846+ ((QUICK_RANGE_SELECT*) sql_select->quick)->num_ranges() <=
2847+ MAX_PARALLEL_SCAN_RANGES)
2848+ {
2849+ return Parallel_scan_type::RANGE_SCAN;
2850+ }
2851+ else
2852+ {
2853+ return Parallel_scan_type::NONE;
2854+ }
2855+ }
2856+ else
2857+ {
2858+ return Parallel_scan_type::FULL_SCAN;
2859+ }
2860+ }
2861+
2862+ extern int parallel_rr_next(READ_RECORD *info);
2863+
2864+ /*
2865+ Snapshot the quick select's key intervals into the `ranges` array.
2866+ An empty result means "scan the whole table".
2867+
2868+ @return true on error (my_error() has been called)
2869+ */
2870+ static bool parallel_build_key_ranges(JOIN_TAB *tab,
2871+ Dynamic_array<KEY_MULTI_RANGE> *ranges)
2872+ {
2873+ if (!tab->use_parallel_scan || !tab->select || !tab->select->quick)
2874+ return false;
2875+
2876+ QUICK_RANGE_SELECT *quick= (QUICK_RANGE_SELECT*) tab->select->quick;
2877+ range_seq_t seq= quick_range_seq_init(quick, 0, 0);
2878+ KEY_MULTI_RANGE range;
2879+
2880+ while (!quick_range_seq_next(seq, &range))
2881+ {
2882+ if (ranges->append(range))
2883+ return true;
2884+ }
2885+ return false;
2886+ }
2887+
2888+ int parallel_init_read_record(JOIN_TAB *tab)
2889+ {
2890+ TABLE *table = tab->table;
2891+ handler *file = table->file;
2892+
2893+ /*
2894+ parallel_init_coordinator() copies what it keeps, so these only have to
2895+ outlive the call. prealloc=0 defers the allocation until there is
2896+ something to store: a full scan contributes no intervals at all.
2897+ */
2898+ Dynamic_array<KEY_MULTI_RANGE> ranges(PSI_INSTRUMENT_MEM, 0, 16);
2899+ if (parallel_build_key_ranges(tab, &ranges))
2900+ return 1;
2901+
2902+ const size_t ARBITRARY_WORKERS_NUM = 4;
2903+ int err= file->parallel_init_coordinator(ARBITRARY_WORKERS_NUM, ranges);
2904+ if (err == HA_ERR_UNSUPPORTED)
2905+ {
2906+ // Fall back to the serial record reader
2907+ tab->read_first_record= join_init_read_record;
2908+ tab->use_parallel_scan= false;
2909+ return join_init_read_record(tab);
2910+ }
2911+ if (err)
2912+ {
2913+ file->print_error(err, MYF(0));
2914+ return 1;
2915+ }
2916+
2917+ tab->read_record.table = tab->table;
2918+ tab->read_record.thd = tab->join->thd;
2919+ tab->read_record.read_record_func = parallel_rr_next;
2920+ tab->read_record.print_error = TRUE;
2921+
2922+ Parallel_worker_ctx *worker_ctx= file->parallel_get_worker_context(0);
2923+ DBUG_ASSERT(worker_ctx);
2924+ err= file->parallel_init_worker(worker_ctx);
2925+ if (err == HA_ERR_END_OF_FILE)
2926+ return -1; // No rows — read_first_record's "empty result" sentinel
2927+ if (err)
2928+ {
2929+ // Real error from the engine
2930+ file->print_error(err, MYF(0));
2931+ return 1;
2932+ }
2933+ tab->read_record.parallel_worker_ctx = worker_ctx;
2934+
2935+ // Fetch the first row before returning — this is what join_init_read_record does.
2936+ return tab->read_record.read_record();
2937+ }
27952938
27962939/*
27972940 @brief
@@ -3581,6 +3724,20 @@ int JOIN::optimize_stage2()
35813724 if (init_range_rowid_filters())
35823725 DBUG_RETURN(1);
35833726
3727+ /*
3728+ Parallel-scan decision must come last: everything above can still
3729+ change the first table's access method.
3730+ */
3731+ {
3732+ JOIN_TAB *first= first_linear_tab(this, WITH_BUSH_ROOTS,
3733+ WITHOUT_CONST_TABLES);
3734+ if (first && !(select_options & SELECT_DESCRIBE) &&
3735+ get_applicable_parallel_scan_type(first) != Parallel_scan_type::NONE)
3736+ {
3737+ first->use_parallel_scan= true;
3738+ first->read_first_record= parallel_init_read_record;
3739+ }
3740+ }
35843741 error= 0;
35853742
35863743 if (select_options & SELECT_DESCRIBE)
@@ -16326,7 +16483,6 @@ void JOIN_TAB::remove_redundant_bnl_scan_conds()
1632616483 set_cond(NULL);
1632716484}
1632816485
16329-
1633016486/*
1633116487 Plan refinement stage: do various setup things for the executor
1633216488
@@ -16662,7 +16818,6 @@ make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after)
1666216818 break;
1666316819 }
1666416820 }
16665-
1666616821 DBUG_RETURN(FALSE);
1666716822}
1666816823
@@ -16796,6 +16951,12 @@ void JOIN_TAB::cleanup()
1679616951 table->file->ha_ft_end();
1679716952 else if (table->hlindex && table->hlindex->context)
1679816953 table->hlindex_read_end();
16954+ else if (use_parallel_scan)
16955+ {
16956+ table->file->parallel_end_worker();
16957+ table->file->parallel_end_coordinator();
16958+ read_record.parallel_worker_ctx= NULL;
16959+ }
1679916960 else
1680016961 table->file->ha_index_or_rnd_end();
1680116962 preread_init_done= FALSE;
0 commit comments