1
0
mirror of https://github.com/MariaDB/server.git synced 2025-08-08 11:22:35 +03:00
Commit Graph

8648 Commits

Author SHA1 Message Date
Monty
fbbc63453b Added current_cost and best_cost to optimizer trace when pruning by cost
This helps a lot when trying to find out why a table combination was pruned
2022-07-26 22:27:29 +07:00
Monty
515b9ad05a Added EQ_REF chaining to the greedy_optimizer
MDEV-28073 Slow query performance in MariaDB when using many table

The idea is to prefer and chain EQ_REF tables (tables that uses an
unique key to find a row) when searching for the best table combination.
This significantly reduces row combinations that has to be examined.
This is optimization is enabled when setting optimizer_prune_level=2
(which is now default).

Implementation:
- optimizer_prune_level has a new level, 2, which enables EQ_REF
  optimization in addition to the pruning done by level 1.
  Level 2 is now default.
- Added JOIN::eq_ref_tables that contains bits of tables that could use
  potentially use EQ_REF access in the query.  This is calculated
  in sort_and_filter_keyuse()

Under optimizer_prune_level=2:
- When the greedy_optimizer notices that the preceding table was an
  EQ_REF table, it tries to add an EQ_REF table next. If an EQ_REF
  table exists, only this one will be considered at this level.
  We also collect all EQ_REF tables chained by the next levels and these
  are ignored on the starting level as we have already examined these.
  If no EQ_REF table exists, we continue as normal.

This optimization speeds up the greedy_optimizer combination test with
~25%

Other things:
- I ported the changes in MySQL 5.7 to greedy_optimizer.test to MariaDB
  to be able to ensure we can handle all cases that MySQL can do.
- I have run all tests with --mysqld=--optimizer_prune_level=1 to verify that
  there where no test changes.
2022-07-26 22:27:29 +07:00
Monty
6e7376eb59 Remove unnecessary testing of join dependency when sorting tables
The dependency checking is not needed when using
best_extension_by_limited_search() as this function ensures
that we don't use tables that are depending on any of the remaning
tables.
2022-07-26 22:27:29 +07:00
Monty
318a74f1aa Added get_allowed_nj_tables() to speed up gready_search()
"Get the tables that one is allowed to have as the next table in the
current plan"

Main author: Sergei Petrunia <sergey@mariadb.com>
Co author: Monty
2022-07-26 22:27:29 +07:00
Monty
b3c74bdc1f Improve pruning in greedy_search by sorting tables during search
MDEV-28073 Slow query performance in MariaDB when using many tables

The faster we can find a good query plan, the more options we have for
finding and pruning (ignoring) bad plans.

This patch adds sorting of plans to best_extension_by_limited_search().
The plans, from best_access_path() are sorted according to the numbers
of found rows.  This allows us to faster find 'good tables' and we are
thus able to eliminate 'bad plans' faster.

One side effect of this patch is that if two tables have equal cost,
the table that which was used earlier in the query is preferred.
This allows users to improve plans by reordering eq_ref tables in the
order they would like them to be uses.

Result changes caused by the patch:
- Traces are different as now we print the cost for using tables before
  we start considering them in the plan.
- Table order are changed for some plans. In most cases this is because
  the plans are equal and tables are in this case sorted according to
  their usage in the original query.
- A few plans was changed as the optimizer was able to find a better
  plan (that was pruned by the original code).

Other things:

- Added a new statistic variable: "optimizer_join_prefixes_check_calls",
  which counts number of calls to best_extension_by_limited_search().
  This can be used to check the prune efficiency in greedy_search().
- Added variable "JOIN_TAB::embedded_dependent" to be able to handle
  XX IN (SELECT..) in the greedy_optimizer.  The idea is that we
  should prune a table if any of the tables in embedded_dependent is
  not yet read.
- When using many tables in a query, there will be some additional
  memory usage as we need to pre-allocate table of
  table_count*table_count*sizeof(POSITION) objects (POSITION is 312
  bytes for now) to hold the pre-calculated best_access_path()
  information.  This memory usage is offset by the expected
  performance improvement when using many tables in a query.
- Removed the code from an earlier patch to keep the table order in
  join->best_ref in the original order.  This is not needed anymore as we
  are now sorting the tables for each best_extension_by_limited_search()
  call.
2022-07-26 22:27:28 +07:00
Oleg Smirnov
49e14000ee MDEV-26427 MariaDB Server SEGV on INSERT .. SELECT
1. For INSERT..SELECT statements: don't include table/view the data
   is inserted into in the list of leaf tables
2. Remove duplicated and dead code related to table_count
2022-07-14 11:07:24 +07:00
Sergei Petrunia
0e9a255ec8 MDEV-28871: Assert ... failed in JOIN::dbug_verify_sj_inner_tables...
optimize_semi_joins() calls update_sj_state() to update semi-join
optimization state in the JOIN class.

greedy_search() algorithm considers different join prefixes,
and then picks one table to put into the join prefix.
Most of the semi-join optimization state is in the table's entry
in the join->positions[cur_prefix_size].

However, it also needs to call update_sj_state() to update the
semi-join optimization state in the JOIN class.

There is one exception, which is the cause of this bug: when we're
inside optimize_semi_join_nests() and are optimizing a subquery,
optimize_semi_joins() does nothing, it doesn't call update_sj_state().

greedy_search() must not do that either.
2022-07-07 22:15:42 +03:00
Marko Mäkelä
b81460f07e Merge 10.8 into 10.9 2022-06-23 13:47:22 +03:00
Marko Mäkelä
b356309840 Merge 10.7 into 10.8 2022-06-23 13:46:23 +03:00
Marko Mäkelä
5d0496c749 Merge 10.6 into 10.7 2022-06-23 13:20:25 +03:00
Vladislav Vaintroub
eb7f46ca1e Merge remote-tracking branch 'origin/10.5' into 10.6 2022-06-23 06:29:57 +02:00
Monty
674842bee0 MDEV-28858 Wrong result with table elimination combined with not_null_range_scan
The bug was that build_notnull_conds_for_range_scans() did not take into
account the join_tab is not yet sorted with constant tables first.
Fixed the bug by testing explicitely if a table is a const table.
2022-06-16 00:12:11 +03:00
Marko Mäkelä
6dea701e0f Merge 10.8 into 10.9 2022-06-09 14:53:34 +03:00
Marko Mäkelä
0af9346079 Merge 10.7 into 10.8 2022-06-09 14:37:53 +03:00
Marko Mäkelä
fe75e5e5b1 Merge 10.6 into 10.7 2022-06-09 14:11:43 +03:00
Marko Mäkelä
a9d0bb12e6 Merge 10.4 into 10.5 2022-06-09 12:22:55 +03:00
Marko Mäkelä
c89e3b70a7 Merge 10.3 into 10.4 2022-06-09 11:53:46 +03:00
Michael Widenius
31811cf81d Make join->key_dependent up to date for derived tables
Main-author: Sergei Petrunia
2022-06-07 20:43:11 +03:00
Michael Widenius
432a4ebe5c Improve table pruning in optimizer with up to date key_dependent map
Part of:
MDEV-28073 Slow query performance in MariaDB when using many tables

s->key_dependent has a list of tables that are compared with key fields
in the current table.  However it does not take into account if a key
field could be resolved by another table.
This is because MariaDB expands 'join_tab->keyuse' to include all generated
comparisons.
For example:
SELECT * from t1,t2,t3 where t1.key=t2.key and t2.key=t3.key
In this case keyuse for t1 includes t2.key and t3.key and key_dependent
contains 't2.map | t3.map'
If we in best_extension_by_limited_search() consider t2,t1 then t1's
key is fully defined, but we cannot do any prune of plans as
s->key_dependent indicates that t3 is still needed.

Fixed by calculating in best_access_patch the current key_dependent map
of tables that is needed to satisfy all keys. This allows us to prune
more bad plans earlier as soon as all keys can be used.

We also set key_dependent to 0 if we found an EQ_REF key, as this an
optimal key for the table and there is no reason to check more keys.
2022-06-07 20:43:11 +03:00
Michael Widenius
64f24b776d greedy_search() and best_extension_by_limited_search() scrambled table order
best_extension_by_limited_search() assumes that tables should be sorted
according to size to be able to quickly disregard bad plans. However the
current usage of swap_variables() will change the table order to a not
sorted one for the next recursive call. This breaks the assumtion and
causes performance issues when using many tables (we have to examine
many more plans).

This patch fixes this by ensuring that the original table order is kept
for the not yet used tables when best_extension_by_limited_search() is
called.

This was done by always calling swap_variables() for each table and
restoring the original table order at exit.

Some test changed:
- In a majority of the test the change was that two "identical tables"
  where swapped and the optimzer is now using the first/smaller table
- In few test the table order was changed. The new plan looks identical
  or slighly better than the original.
2022-06-07 20:43:10 +03:00
Sergei Petrunia
f0ea7f7f33 MDEV-28749: restore_prev_nj_state() doesn't update cur_sj_inner_tables correctly
(Try 2)

The code that updates semi-join optimization state for a join order prefix
had several bugs. The visible effect was bad optimization for FirstMatch or
LooseScan strategies: they either weren't considered when they should have
been, or considered when they shouldn't have been.

In order to hit the bug, the optimizer needs to consider several different
join prefixes in a certain order. Queries with "obvious" query plans which
prune all join orders except one are not affected.

Internally, the bugs in updates of semi-join state were:
1. restore_prev_sj_state() assumed that
  "we assume remaining_tables doesnt contain @tab"
  which wasn't true.
2. Another bug in this function: it did remove bits from
   join->cur_sj_inner_tables but never added them.
3. greedy_search() adds tables into the join prefix but neglects to update
   the semi-join optimization state. (It does update nested outer join
   state, see this call:
     check_interleaving_with_nj(best_table)
   but there's no matching call to update the semi-join state.
   (This wasn't visible because most of the state is in the POSITION
    structure which is updated. But there is also state in JOIN, too)

The patch:
- Fixes all of the above
- Adds JOIN::dbug_verify_sj_inner_tables() which is used to verify the
  state is correct at every step.
- Renames advance_sj_state() to optimize_semi_joins().
  = Introduces update_sj_state() which ideally should have been called
    "advance_sj_state" but I didn't reuse the name to not create confusion.
2022-06-07 20:43:10 +03:00
Monty
46c4fd45c3 Fixed cost calculation for SELECT STRAIGHT_JOIN
Main fix was replacing read_time+= with read_time

I also did updated the 'identical' code in optimize_straight_join) and
best_extension_by_limited_search() to make them eaiser to compare.

Reviewer: Sergei Petrunia <sergey@mariadb.com>
2022-06-07 20:43:10 +03:00
Sergei Petrunia
19c721631e MDEV-28749: restore_prev_nj_state() doesn't update cur_sj_inner_tables correctly
(Try 2) (Cherry-pick back into 10.3)

The code that updates semi-join optimization state for a join order prefix
had several bugs. The visible effect was bad optimization for FirstMatch or
LooseScan strategies: they either weren't considered when they should have
been, or considered when they shouldn't have been.

In order to hit the bug, the optimizer needs to consider several different
join prefixes in a certain order. Queries with "obvious" query plans which
prune all join orders except one are not affected.

Internally, the bugs in updates of semi-join state were:
1. restore_prev_sj_state() assumed that
  "we assume remaining_tables doesnt contain @tab"
  which wasn't true.
2. Another bug in this function: it did remove bits from
   join->cur_sj_inner_tables but never added them.
3. greedy_search() adds tables into the join prefix but neglects to update
   the semi-join optimization state. (It does update nested outer join
   state, see this call:
     check_interleaving_with_nj(best_table)
   but there's no matching call to update the semi-join state.
   (This wasn't visible because most of the state is in the POSITION
    structure which is updated. But there is also state in JOIN, too)

The patch:
- Fixes all of the above
- Adds JOIN::dbug_verify_sj_inner_tables() which is used to verify the
  state is correct at every step.
- Renames advance_sj_state() to optimize_semi_joins().
  = Introduces update_sj_state() which ideally should have been called
    "advance_sj_state" but I didn't reuse the name to not create confusion.
2022-06-07 18:48:44 +03:00
Sergei Golubchik
bf2bdd1a1a Merge branch '10.8' into 10.9 2022-05-19 14:07:55 +02:00
Sergei Golubchik
b7ffccf49b Merge branch '10.7' into 10.8 2022-05-18 13:26:48 +02:00
Sergei Golubchik
99a433ed1c Merge branch '10.6' into 10.7 2022-05-18 10:34:38 +02:00
Sergei Golubchik
b2187662bc Merge branch '10.5' into 10.6 2022-05-18 10:30:47 +02:00
Sergei Golubchik
7970ac7fe8 Merge branch '10.4' into 10.5 2022-05-18 09:50:26 +02:00
Sergei Golubchik
23ddc3518f Merge branch '10.3' into 10.4 2022-05-18 01:25:30 +02:00
Michael Widenius
e03e72234a Fixed warning from UBSAN
The warning comes from copying POSITION objects where 'type' is not
initialized.  This does not affect any production code as when 'type'
was compared/used, it was always initialized.

Removed by initializing the type variable in the constructor
2022-05-15 17:59:43 +03:00
Sergei Petrunia
730eb1c4be Code cleanup in/around check_interleaving_with_nj()
- In best_extension_by_limited_search(), do not check for
  "(remaining_tables & real_table_bit)", it is guaranteed to be true.
  Make it an assert.
- In (!idx || check_interleaving_with_nj())", remove the !idx part.
  This check made sense only in the original version of this function.
- "micro optimization" in check_interleaving_with_nj().
2022-05-15 11:10:36 +03:00
Monty
b729896d00 MDEV-28073 Query performance degradation in newer MariaDB versions when using many tables
The issue was that best_extension_by_limited_search() had to go through
too many plans with the same cost as there where many EQ_REF tables.

Fixed by shortcutting EQ_REF (AND REF) when the result only contains one
row. This got the optimization time down from hours to sub seconds.

The only known downside with this patch is that in some cases a table
with ref and 1 record may be used before on EQ_REF table. The faster
optimzation phase should compensate for this.
2022-05-12 10:01:10 +03:00
Sergei Golubchik
443c2a715d Merge branch '10.7' into 10.8 2022-05-11 12:21:36 +02:00
Sergei Golubchik
fd132be117 Merge branch '10.6' into 10.7 2022-05-11 11:25:33 +02:00
Sergei Golubchik
3bc98a4ec4 Merge branch '10.5' into 10.6 2022-05-10 14:01:23 +02:00
Sergei Golubchik
ef781162ff Merge branch '10.4' into 10.5 2022-05-09 22:04:06 +02:00
Sergei Golubchik
a8e57906d1 10.4 specific fixes for DEFAULT() 2022-05-09 12:21:02 +02:00
Sergei Golubchik
a70a1cf3f4 Merge branch '10.3' into 10.4 2022-05-08 23:03:08 +02:00
Sergei Golubchik
79660e59ee can't use Item_default_value as a field if it's an expression OR a blob
Fixed failing main.default on Windows
(to trigger an assert the test needed a debug build without
safemalloc, as 0xa5 happened to have the important bit set "correctly")
2022-05-08 13:00:35 +02:00
Sergei Golubchik
6f741eb6e4 Merge branch '10.2' into 10.3 2022-05-07 11:48:15 +02:00
Oleksandr Byelkin
141ab971d8 MDEV-28402 ASAN heap-use-after-free in create_tmp_table, Assertion `l_offset >= 0 && table->s->rec_buff_length - l_offset > 0'
Make default() function follow Item_field and use get_tmp_table_item() for
change_to_use_tmp_fields().
2022-05-06 10:00:08 +02:00
Sergei Petrunia
8dbfaa2aa4 MDEV-28437: Assertion `!eliminated' failed in Item_subselect::exec
(This is the assert that was added in fix for MDEV-26047)

Table elimination may remove an ON expression from an outer join.
However SELECT_LEX::update_used_tables() will still call

  item->walk(&Item::eval_not_null_tables)

for eliminated expressions. If the subquery is constant and cheap
Item_cond_and will attempt to evaluate it, which will trigger an
assert.
The fix is not to call update_used_tables() or eval_not_null_tables()
for ON expressions that were eliminated.
2022-05-05 18:58:25 +03:00
Oleksandr Byelkin
9614fde1aa Merge branch '10.2' into 10.3 2022-05-03 10:59:54 +02:00
Sergei Golubchik
8c34eab968 MDEV-28094 Window function in expression in ORDER BY
call item->split_sum_func() in setup_order() just as
it's done in setup_fields()
2022-04-29 17:05:19 +02:00
Sergei Golubchik
bc9102eb81 cleanup: (*order->item) -> item
and minor style fixes
2022-04-29 17:05:12 +02:00
Sergei Petrunia
3f68c2169e MDEV-28201: Server crashes upon SHOW ANALYZE/EXPLAIN FORMAT=JSON
- Describe the lifetime of EXPLAIN data structures in
  sql_explain.h:ExplainDataStructureLifetime.

- Make Item_field::set_field() call set_refers_to_temp_table()
  when it refers to a temp. table.
- Introduce QT_DONT_ACCESS_TMP_TABLES flag for Item::print.
  It directs Item_field::print to not try access its the
  temp table.
- Introduce Explain_query::notify_tables_are_closed()
  and call it right before the query closes its tables.
- Make Explain data stuctures' print_explain_json() methods
  accept "no_tmp_tbl" parameter which means pass
  QT_DONT_ACCESS_TMP_TABLES when printing items.
- Make Show_explain_request::call_in_target_thread() not call
  set_current_thd(). This wasn't needed as the code inside
  lex->print_explain() uses output->thd anyway. output->thd
  refers to the SHOW command's THD object.
2022-04-29 10:48:26 +03:00
Oleg Smirnov
02c3babdec MDEV-28124 Server crashes in Explain_aggr_filesort::print_json_members
SHOW EXPLAIN/ANALYZE FORMAT=JSON tries to access items that have already been
freed by a call to free_items() during THD::cleanup_after_query().
The solution is to disallow APC calls including SHOW EXPLAIN/ANALYZE
just before the call to free_items().
2022-04-29 10:48:25 +03:00
Marko Mäkelä
504a3b32f6 Merge 10.8 into 10.9 2022-04-28 15:54:03 +03:00
Marko Mäkelä
133c2129cd Merge 10.7 into 10.8 2022-04-27 10:43:00 +03:00
Marko Mäkelä
638afc4acf Merge 10.6 into 10.7 2022-04-26 18:59:40 +03:00