Problem/Motivation
Having a Condition class that can be overriden by a third party database is important. The Condition class generates a significant amount of SQL string parts. The currently by Drupal core supported databases may not need such a class, but that does not mean that a third party database driver will not need it. It is fantastic that Drupal core supports more then only MySQL but lets not limit it by only adding what is needed for the by core supported database drivers.
Proposed resolution
Making the class Drupal\Core\Database\Query\Condition overridable by a database driver.
Remaining tasks
- Write patch
- Review patch
User interface changes
None.
API changes
Yes. Third party database drivers need to add a Condition class to their driver.
Data model changes
None.
The old issue summary
pwolanin just pointed me to #592522: Hooks node_type, taxonomy and user knocks out our database server.
Digest version: The Apache Solr module has some queries that involve UPDATES with large subselects. Now, we support subselects in UPDATE queries just fine... as subselects. Unfortunately, MySQL's handling of subselects is crap, because it reruns the query for each record (in a select), does stupid stuff with temp tables (disk thrashing), etc. That's fine when the subselect gets a dozen records. When it's selecting a few hundred thousand taxonomy terms (as Solr does), MySQL stops off to its room and slams the door.
MySQL has an alternate syntax that uses Joins(!!!) within the Update query that is a lot faster, and the Solr D6 module is using that now. The D7 version, of course, is not, because it's being a good little module and using the DB API properly. UpdateQuery will therefore dutifully run the ANSI-proper but MySQL-horrid version, and the server will fall over. Fail.
So our options are:
1) Tell modules running such queries to check the DB type (that is still possible) and if they're on MySQL use db_query() directly with an Update query that does the MySQL-proprietary version. If not on MySQL, just do the normal db_update(). This would be the least effort for us in core.
2) Override __toString() for UpdateQuery_mysql and try to detect if we're dealing with a subselect that we can optimize, then try to optimize it. This would be the most elegant solution and the best for contrib, but I don't know how feasible it is in practice.
3) Peter suggested adding tagging support to UpdateQuery so that modules could tag them as "needs subselect optimization" and then offer suggestions about how to optimize the query. This would be a sort of extended version of #2, but is a deeper structural change.
I am open to suggestions as to the best route forward.
| Comment | File | Size | Author |
|---|---|---|---|
| #24 | 961604-large-subselect-plumbing.patch | 6.1 KB | Crell |
| #11 | 961604-double-wrapping-select.patch | 2.49 KB | damien tournoud |
Comments
Comment #1
pwolanin commentedMarking this as major - since #1 is clearly discouraged.
Comment #2
Anonymous (not verified) commentedblurgh, mysql sure has some sharp bits.
i guess we need committer feedback.
i think i'd favour #3, only because #2 sounds hard to get right. at least with #3, any mistakes would be more explicit.
Comment #3
webchickI have no idea why you need committer feedback when this hasn't even been discussed yet. Please call me in when things are deadlocked and there's no way forward. I can't commit when I'm feedbacking.
Comment #4
pwolanin commentedThe pattern we are trying to match is simple enough that it feels like #2 should be feasible in at least osme cases.
I'd still love to see a query tag/alter for everything, not just selects.
Comment #5
chx commentedYou can double up your subselects to avoid the problem http://www.xaprb.com/blog/2006/04/30/how-to-optimize-subqueries-and-join...
Comment #6
Crell commentedIt sounds like chx has a viable fix, and I think it's a sufficient edge-case that most modules won't hit it. Also talking to webchick we decided that we're not going to go with the API changing approach (#3), so this isn't a 7.0 blocker.
Worst case there's also workarounds (using db_query()).
Marking back down to normal.
Comment #7
pwolanin commentedWhere's the fix? I don't see in the article that this works for UPDATE queries, which was the original focus of this issue.
Comment #8
Crell commentedCan you try doing a double-subselect as the issue mentions, and see what happens?
Comment #9
pwolanin commentedfrom IRC:
So #2 would be an API addition. #1 is less efficient than the JOIN method, but easier to put in core. #3 is easiest, but leaves the rest of contrib and core out in the code.
Comment #10
Crell commentedGiven that we're on the cusp of an RC, I'd favor #1. It should still work well enough here, and it means we don't have to change anything in core.
And I find it quite amusing that you said "out in the code". :-)
Comment #11
damien tournoud commentedRough starting point.
Comment #12
chx commentedThe issue has absolutely nothing to do with updates. Subselects suck on SELECT or UPDATE equally. So while Damien's patch is right it just needs to verify the existence of a subselect and if it's not marked up for prevention then yes probably wrap it up.
Edit: In some cases, the optimizer code can execute the subquery for every row. That's not true. The truth is In every case MySQL runs the subquery for every row. MySQL only has dependent subqueries, no matter what. Anonymous views are, however, not translated into dependent subqueries only because that's simply not possible as the tables need to be resolved before the result set can even exist so independent subqueries are moved to an anonymous view.
Comment #13
Crell commentedHere's a crazy idea. Currently, I think DatabaseCondition is one of the only classes that a given DB driver cannot extend and customize. Should they be able to? Would it make more sense to put this double-wrapping into a DatabaseCondition_mysql class?
I'm not sure what the knock-on effect of that would be off hand, but I think it could be done without an external API change. At worst it would only affect drivers, which are quite few.
Someone tell me that's a stupid idea... :-)
Comment #14
pwolanin commented@Crell - putting it into the condition object might be cleaner, since that's logically where it lives.
Comment #15
pwolanin commentedRevised patch fixing the comments and making this apply in all cases where we are running a subquery.
Comment #16
Crell commentedOK, so it's a very good thing I spent time this weekend working on this issue as in the process I stumbled across a bug in the conditional compiler for subqueries. :-) It wasn't preprocessing the query properly, so sub-sub queries were dying completely.
The attached patch does 3 things:
1) Fix the aforementioned bug.
2) Make it possible for drivers to implement their own DatabaseCondition classes, just like they can for everything else.
3) Add a MySQL-specific DatabaseCondition class that preprocesses subqueries in where clauses into wrapped FROM subqueries, as per the article chx linked above.
It should be transparent for any existing well-behaved code, but very-big WHERE subqueries in MySQL should (in theory) just kinda get faster as a result of this patch.
Comment #17
Crell commentedSame thing but with less-wrong indentation.
Comment #18
damien tournoud commentedAs already mentioned on IRC, I'm rather reluctant to wrap *all* subqueries into an additional SELECT. My reasoning is:
DELETE FROM table WHERE id IN (SELECT id FROM table WHERE id > 0)will trigger an error). In that case the functionality is very broken, and it makes sense to fix it.For example, on MariaDB 5.1:
I had to interrupt the double-wrapper subquery, it ran for more then 20 minutes.
Comment #19
Crell commentedWell fart.
I have to veto forking the logic by MySQL version. That way lies madness. But I don't know what else to do here. Do we know what versions of MySQL don't suck on WHERE subqueries?
(Either way the rest of the patch should still go in, IMO, even if we don't subclass for MySQL in this case.)
Comment #20
Crell commentedPer discussion with webchick, attached is a patch that just does #1 and #2 above. There should be no net effect from this patch other than a bug fix and allowing drivers to have their own condition classes. We can then debate what if anything to do with the MySQL condition class later, as that's not an RC-sensitive fix but the other two are.
Comment #21
pwolanin commentedSo this patch doesn't fix the bug?
How can we let a condition know that it's a condition on a update/delete versus a select?
Comment #22
Crell commented#20 sets up extra flexibility to fix the performance issue mentioned in #0. Per #18 it may not be so simple to fix due to conflicting server versions. :-( The extra flexibility and bug fix should go in either way, IMO.
Comment #23
chx commentedThe doxygen is totally useless. The rest is probably useful, yes.
Comment #24
Crell commentedRevised docblock.
Comment #25
Crell commentedSomeone want to RTBC this? Please?
Comment #26
moshe weitzman commentedMore flexibility is good. Bot is happy, and we know that DBTNG is heavily tested.
Comment #27
webchick#24: 961604-large-subselect-plumbing.patch queued for re-testing.
Comment #28
catchComment #29
catch#24: 961604-large-subselect-plumbing.patch queued for re-testing.
Comment #30
catchComment #31
pwolanin commentedWaiting on this for resolution to apachesolr issue #592522: Hooks node_type, taxonomy and user knocks out our database server
Comment #32
webchickSo it looks like the API change part of this would be internal to db_or() and db_and(), so existing modules should be unaffected. Yes?
However, I'm not seeing test coverage for:
a) The bug Crell found in #16.
b) Confirmation that the problem with crazy sub-queries like the one pwolanin wants to do in ApacheSolr is fixed.
I'm not sure if b) is possible, but it seems a) should be. Confirmation either way from pwolanin about this fixing the bug would be good.
Comment #33
Crell commentedUgh. Hell, I don't even remember what I was doing 8 months ago. I've no idea how to write a test for it now.
Comment #34
sunI'm tempted to classify this as a feature request, but let's call it task in terms of "oversight" for now. Definitely not a bug.
Comment #35
nick_vhJust as an update. Because of this missing feature we currently have to write our query in the module as the example below because mysql has a bug that prevents us from using the IN with a subquery.
Could you tell us how the patch above would fix this problem?
Comment #36
pwolanin commentedsee #16:
3) Add a MySQL-specific DatabaseCondition class that preprocesses subqueries in where clauses into wrapped FROM subqueries, as per the article chx linked above.
Comment #37
tim.plunkettRecategorizing.
Comment #38
jhedstromProbably needs to wait to at least 8.1.
Comment #41
jhedstromLast patch is from 6 years ago, and last real discussion from 5 years ago. Marking postponed since the IS needs updating if this is still relevant for Drupal 8.
Comment #42
daffie commentedComment #56
smustgrave commentedThank you for sharing your idea for improving Drupal.
We are working to decide if this proposal meets the Criteria for evaluating proposed changes. There hasn't been any discussion here for over 8 years which suggests that this has either been implemented or there is no community support. Your thoughts on this will allow a decision to be made.
Since we need more information to move forward with this issue, the status is now Postponed (maintainer needs more info). If we don't receive additional information to help with the issue, it may be closed after three months.
Thanks!
Comment #58
smustgrave commentedWanted to bump 1 more time if still needed?