[MDEV-22180] Planner opens unnecessary tables when updated table is referenced by foreign keys Created: 2020-04-07 Updated: 2022-12-09 Resolved: 2020-05-05 |
|
| Status: | Closed |
| Project: | MariaDB Server |
| Component/s: | Storage Engine - InnoDB |
| Affects Version/s: | 10.1.40, 10.1.44, 10.2.31, 10.3.22, 10.4.12, 10.5.2 |
| Fix Version/s: | 10.1.45, 10.2.32, 10.3.23, 10.4.13 |
| Type: | Bug | Priority: | Major |
| Reporter: | Guillermo Schulman | Assignee: | Sergei Golubchik |
| Resolution: | Fixed | Votes: | 4 |
| Labels: | explain, foreign-keys, performance, plan, planner | ||
| Environment: |
Ubuntu |
||
| Attachments: |
|
||||||||||||||||||||||||||||||||||||||||||||
| Issue Links: |
|
||||||||||||||||||||||||||||||||||||||||||||
| Description |
|
When performing a writing query on a referenced table, the planner opens all the tables referencing it, even if the modified fields are not the referenced ones and even if no record matches the WHERE clause. This behavior started to happen at some version between 10.1.20 and 10.1.40, as it is reproducible in the later and not in the former.
Then just access that DB and run this explain statement:
It will take quite long and it seems that the planner is opnening all those referencing tables. To see it more clearly here are some evident tests: In version 10.1.40 (or any later version):
In version 10.1.20 looks quite better and does it in 0 miliseconds
Additionally, you can see it in the profiling detail for the failing version:
As you can see, all the time is spent opening tables. |
| Comments |
| Comment by Guillermo Schulman [ 2020-04-07 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
The behavior is reproducible regardless file-per-table is enabled or not. | |||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Guillermo Schulman [ 2020-04-07 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
Just wanted to add that, of course, running the UPDATE without the explain takes the same resources and time which seems to be obvious as it runs the planner before. I did not mention it as I considered it obvious. | |||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Elena Stepanova [ 2020-04-13 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
Thanks for the report. The difference was introduced by this commit in 10.2.3
and later by the backport to 10.0.37 / 10.1.36
From the sound of it, it might have been intentional, I'll leave it to serg to determine it. The MTR version of the test case:
After the change, the second show status returns
Before the change
| |||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Sergei Golubchik [ 2020-04-14 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
It is intentional, indeed. The server needs to open/lock all tables that participate in a query, at least for
Still, let's keep this issue open — we'll try to reduce the overhead. For example, both issues above only apply to CASCADE, not to RESTRICT. With RESTRICT we, quite possibly, don't need to open child tables. Either we can ignore them completely (but beware | |||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Guillermo Schulman [ 2020-04-14 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
Thanks Sergei and Elena. | |||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Sergei Golubchik [ 2020-04-14 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
1) yes, if referenced columns aren't touched it looks like FK tables don't have to be opened. But I'm not sure we have this information when tables are being opened. 2) virtual columns: we need to open the child table to know whether it has indexed virtual columns, we cannot know it without opening. Still, we can open only the frm and not open the table in the engine if it doesn't have virtual columns 3) yes, the bug with ALTER TABLE was already in 10.1, I presume. Indexed virtual columns were introduced only in 10.2, so they couldn't be an issue in 10.1. | |||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Guillermo Schulman [ 2020-04-15 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
Thanks Sergei. Some comments/questions for each item: To be honest, this bug is really preventing us to go on with the upgrade from mariaDb 10.1.20. This is really bad news for us if we wanted to keep on using MariaDb. | |||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Sergei Golubchik [ 2020-04-17 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
1. No, it doesn't need to open children tables for that. Normally MariaDB opens all tables that will be needed for a statement in one step, at the beginning of statement processing. This is done to avoid deadlocks — all tables are locked in the same order, so deadlocks are impossible. Analyzing affected fields and mapping them to tables (e.g. in SELECT a FROM t1, t2 — what table does a belong to?) is done later. That is, first MariaDB opens all [possibly] affected tables, then it finds out whether referenced columns are being updated. May be it can be fixed, but it won't be a simple change. 2. For CASCADE/SET NULL/SET DEFAULT an UPDATE can cause a child table to be updated. If the referencing column is indexed — the index needs to be updated. If the referencing column is a part of the multi-column index and another column in the index is a virtual one — InnoDB will need to know the value of the virtual column to be able to update the index. To evaluate a virtual column the server will need the table open, otherwise it won't even know what SQL expression to evaluate. 3. No, this was a fix for But your test case uses RESTRICT and I suspect it can be fixed easily | |||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Guillermo Schulman [ 2020-04-17 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
Thanks Sergei. | |||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Greg Smethells [ 2021-02-10 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
This is still not fixed. Why is it closed? | |||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Greg Smethells [ 2021-02-10 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
We generated the sample db from that bug ticket and it is indeed slow, on version 10.2.36:
| |||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Greg Smethells [ 2021-02-10 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
It is also slow on version 10.5.8 | |||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Sergei Golubchik [ 2021-02-12 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
gsmethells, that depends on what you mean by "fixed". If you mean that the server still opens referenced tables, and you expected it not to do that just as it was not doing it before — this isn't likely to happen, not doing it was a bug that allowed concurrent DDLs on fk-referenced tables. It could've caused crashes or data corruption. The fix implemented here was to reduce the overhead and keep the DDL protection without actually opening tables when possible. What is your use case? Perhaps we've missed it. Or perhaps it is not possible to avoid opening of tables. Can you show your CREATE TABLE statements? | |||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Dan Sobczak [ 2021-02-12 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
Hello, Our use case is similar to the example above. We have a single authentication table as well as ~16k foreign key constraints referencing that table. Our foreign keys appear to be using the RESTRICT scenario, which as you stated above may be able to skip opening tables:
Here are some CREATE TABLE statements. What we are seeing is any UPDATE on our authentication table has slowed down considerably from 10.1.32. Single authentication table:
Access table (one of many):
| |||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Dan Sobczak [ 2021-02-12 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
Example of a slow query, "failures" is not referenced by any foreign key so this UPDATE being slow is surprising.
| |||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Nikita Malyavin [ 2022-12-09 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
Hello dsobczak, gschulman and gsmethells! You were right, a time to open tables is not just suspicous. I've been working on a few bugs around foreign keys, namely MDEV-30103, where I've seen one of the possible solutions in making a complete bidirectional prelocking and pre-opening. Your report became an obstacle for that solution. I decided to find out what is taking so much time, and is it really because of ha_innobase::open taking long. As you may see, most of the time spent was in MDL_context::find_ticket and prepare_fk_prelocking_list. I don't know how __divtf3 ended up there, it's probably some symbol missing in my debug libc build. Anyway there are two recognized functions taking 60% of time. find_ticket traverses a list of all MDL tickets, which is at least one ticket per table. prepare_fk_prelocking_list does nothing weighty except calling find_fk_prelocked_table, which also traverses a list, of opened tables this time. This means that open_tables took O(tables^2) time! Sounds nonsense for me, but I guess we just didn't expect so many tables to open in one query, it wasn't supposed as a normal use case. It was no big difference with or without fix on the structure of the report. It's only that find_ticket somehow took more time (according to a number of samples reported by perf) in the reverted fix case. I have opened MDEV-30182 to implement an optimization. Such kind of work is usually only done in the major versions. Thank you for your alarms! |