-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DDC-357] Effective toOne joins #970
[DDC-357] Effective toOne joins #970
Conversation
- [DDC-357] Real Lazy-loading toOne for inverse side: when the relation is not EAGER and is inverse it will be hydrated as proxy - Eager toOne relations are automatically joined to the generated SQL and always loaded
Hello, thank you for creating this pull request. I have automatically opened an issue http://www.doctrine-project.org/jira/browse/DDC-3011 We use Jira to track the state of pull requests and the versions they got |
I wouldn't call it effective toOne: for the inverse side, you are joining the table to get the id, and then querying the same table again to load other data when the proxy is initialized. This is where you are wasting queries. Note that lazy does not mean you will always get a proxy, but that Doctrine can use a proxy. If the entity was already fully loaded in the identity map, it will use it, not a separate proxy |
@stof that's not exactly true, It's a toOne relation, if I knew I would use the entire entity, I would have joined it in the DQL. This only means I have more control over how the data is loaded. With the current solution, I always get 1+N queries no matter what, the only solution is to hack it with partials which sickens me. I know about identity map and I'm not requiring to always have a proxy. It's just a hint for the SqlWalker and ObjectHydrator - they will try to get me proxy to save resources. If it's already in memory, who cares? |
I like this! 👍 @stof I agree there is a waste of querying same table twice however you have to do the same thing now (for better performance) manually. If you need the relation loaded then you should write the join independently on this. This does not help to optimize querying relations you use but it helps to optimize querying relations you do NOT use. An that is the point IMO. |
@fprochazka assuming that FKs and constraints are in place and guarantee integrity of the association at db level, can we completely skip the joins? |
@Ocramius Do you mean that FKs and constraints at db level affects joining and creating of proxies at Doctrine level? How it could be possible? |
@stekycz the FK constraints are really there to ensure that an associated entity exists, otherwise it's quite useless. Re-thinking of it, the join cannot be avoided. |
@Ocramius I am not sure I understand what you mean. FK could have |
@beberlei @FabioBatSilva may I please have your thoughts on this before I continue? |
ping |
@@ -119,7 +119,17 @@ protected function prepare() | |||
|
|||
$sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]]; | |||
$sourceClass = $this->getClassMetadata($sourceClassName); | |||
$assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; | |||
$assocName = $this->_rsm->relationMap[$dqlAlias]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this?
This requires way too many changes. I don't like that very much. I disagree that this needs solving in such a big refactoring. You could solve this in your application by not mapping the inverse bi-directional one to one association and either use an explicit service or a more active record approach to do something like this:
This is very simple to write on your side and doesn't need so strict changes in the ORM. |
@beberlei I hardly disagree because it is very uncomfortable when you have a lot of OneToOne relations in your system. In real world you need bidirectional access many times (for example to write joins in DQL). I hacked this for a short time by usage ManyToOne relation and created API which looks like OneToOne but it is a hack, not correct solution. However it is better then your suggestion I think because it is not so effective. Yes I can update your suggestion to use private property to keep loaded entity but I have to write it by my own for every OneToOne relation which I need. I think this should be solved by ORM because it is not my job to solve imperfection of framework I use. And a question at the end. Why did you close this PR without waiting for any reaction by community and author? |
@stekycz You may call it the imperfection of the library at work here, but it is the impedance mismatch and bidirectional one-to-one, which is problematic even without an ORM, because it either requires many LEFT JOINs, n+1 queries, or NULL foreign keys. As for your other arguments:
Given all this, I am not willing to accept this PR in this state, so i closed it. We can discuss better solutions here and then maybe have a new PR. |
Thanks for your response! I agree that it is problematic but I do not see better solution then mentioned refactoring. Current solution has problems you are talking about (many LEFT JOINs or n+1 queries) using Doctrine and this PR solves it in the way you do not like.
Do I understand well you accept solution of this only if there will not be many changes in code? Why? Are not there tests to check if everything is ok? |
I don't understand why have you closed the pullrequest. What does it mean? Delete all and start again? Major features suggest there might be required major code changes. And again, I still think this should be solved by ORM. I believe my changes made the querying much better and disagree with you about adding SQL to the result of translating DQL. I've provided solution that might need a bit more work but is several levels better than the current solution. I understand why you're trying to achieve pure code desing but what good is a clean and shiny tool that does shitty job? |
Let's be constructive here for a while. This is a hude deal for us and we need the ORM to solve this effectively. Why is my solution bad and in what classes/levels of abstraction would you implement the functionality? |
@fprochazka Currently FETCH_ modes do not affect the SQL generation at all and I want to keep it that way. The idea is that there should be no optimization of SQL by the SQLWalker to make DQL as simple as possible to understand, given its complexity already. The approach in your PR is therefore not acceptable from a high level POV. To describe the risk of this patch, you add Adding to that the refactoring changes alot in the SqlWalker and adds properties on cached objects (ResultMapping) that affect the performance of ALL queries, not just the one executed. Currently the Hydrators+UnitOfWork can do optimization via query hints, i.e. However the central point again, inverse bidirectional one-to-one on the main entity is problematic from an ORM<->relational perspective and the best way is to avoid it completely. |
Ok, so tu sum it up:
Sounds good? If I work on it this way (hydrator+UoW level), is there a chance we will solve the problem together and get it merged eventually? |
Sounds good, the fourth thing is actually supported already. I don't mind if this creates more code (it will probably than the current proposal), but it will be rather isolated from the rest. |
I'm really happy we came to an understanding and I will work on this as soon as I can :) |
@fprochazka could you continue work on issue please? |
Currently I cannot, but I have it planned. |
Looking forward to a solution to this. |
+1 Also looking forward to this. It creates very frustrating performance situations for those of us with many OneToOnes. |
👍 Any updates/plans with this issue? |
ping |
Instead of pinging, please pick it up yourselves if it's relevant. |
+1 this needs to be implemented, wasted so much time and read so many articles and Q&A, before coming here. Please add extra property or using extra_lazy loading, so everyone have option to do what they want and not doctrine forcing them to follow there rules and there ways. Please have this fixed. Thank you. |
I would also like that. Currently a workaround for me was to change all the OneToOne relations into OneToMany / ManyToOne and have getters / setters as:
Although that did break, if I asked:
Because first() seems to return false in the case, when there are no objects. So the getter could be a little better. |
I had to do same thing, but Ideally there should be settings for doing on demand request as per developer |
+1 need this |
Hi everyone, I've also came up with this issue, and just can't believe, is it just really happening. |
+1 |
1 similar comment
+1 |
An architecture for the solution was suggested in #970 (comment) but nobody is working on this task currently. Writing a +1 is useless here, as it is only about sending a notification email to all subscribers. |
The thing is most people don't know how to implement it, so it makes sense to let the ones that know how to do it, do it, since they'd do a better job. Doctrine is pretty big for someone to learn how it works and implement this feature properly. Most people having this problem come from Symfony, and the only thing we know about Doctrine is how to make queries, and not that much about how it works, or its internals. But this issue creates a lot of problems and hasn't been fixed for years, requiring us to do ugly fixes or not use OneToOne at all. |
revisiting year later just to add +1 |
+1 |
As you might have guessed, my plans changed and I don't have capacity to fix this. Feel free to pick it up, if you need it :) |
Tell me please, what if we not use voted workaround for oneToOne but use this? Do we still have same bug? |
@BonBonSlick I'm unsure how doctrine would handle this. In example 1, the oneToMany relationship to AtricleAttribute is a collection. So doctrine will be able to proxy this fine. (See here for a description about that) On the inverse side, I assume due to the composite key being the article id, and primary keys not being nullable. Doctrine might be able to load a proxy correctly here too. In the example 2 thou. I assume you'd have the same problem. The bidirectional relationship isn't mapped in the example. But If you did map it by adding an address field to the user, you'd have the same problem. Because the address could be null, doctrine will be unable to proxy this and will query to see if an address does exist. p.s. My understanding of this could be completely wrong. I've just come across this issue in one of my own projects today and have spent the day researching 😂 |
+1 |
@syther101 There are really 3 ways doctrine could handle this (plus 2 variations of loading).
I think the best solution would be to allow user to pick the method using "fetch" setting. |
@hubertnnn The first solution you suggested needs to assume that Would it also be an option to add a
|
What is this?
I've kind of rewriten querying of toOne relations. It is more data and query-count effective.
How?
Let's demonstrate it on
CmsUser
andCmsAddress
from tests models. Let's solve behaviour for toOne relations that are not mentioned in the query.lazy + mapped by side
Already implemented, result is that CmsAddress would be proxy.
lazy + inverse side
CmsUser
hasCmsAddress
relation that is mapped and owned byCmsAddress
entity.What has to happen? The identifier of
CmsAddress
cannot be loaded from users table nad has to be added automatic join for the table. Because it's lazy it will be hydrated as proxy, becase that is exactly what I've asked for.If it would have been eagerly loaded, It would create 1+N queries problem that I'm trying to avoid with this. I have the relation as lazy, if I knew I would have needed it and wanned to optimized, I'd join it, but I didn't.
Result is therefore
CmsUser
entity +CmsAddress
proxyeager - both inverse and mapped by sides
The appropriate query component is generated with autojoin and auto selecting of the entity.
If it is self-referencing, the auto join is not generated becase it would cause infinite recursion.
Why?
I've given this a lot of thought and tested it on our not-so-small application. We have unfortunately lot of entitiy relations that are mapped on the inverse side than we need to select, which is effectively killing performace DDC-357
I would have to go and list all the entities as partials to save performace creating such monsters as this
We all know that hydrating a large result set is a bottleneck and if I say the relation is lazy and I'm not joining it I really don't want it to be joined with all it's data!
Now imagine I just want to select few orders and render some data on the page.. I have tens of queries like this just because I have to. This is wrong that the ORM is tripping my feet like this.
What now?
I know I have to solve theese:
Any suggestions? Let's have a reasonable discussion, please don't just close this, I've put a lot of effort into this.