-
Notifications
You must be signed in to change notification settings - Fork 824
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
unique index / indexes are error prone #6819
Comments
Or better... don't write a blank initial record in the first place. Curious: Why is this even being done? If there is no compelling reason, or if there are more issues created than it resolves, why keep it around? Why not just write the base class along with child classes to their respective tables, along with the appropriate data, all in one insert per table? Conjecture here: If this is in order to secure consistent table ID's across child objects, can this not also be handled in a single DB transaction (not single query) and then only allow this sort of behavior as a fallback for connectors for DBMS's which don't support transactions? Assuming that is why this is done (for ID's and backward/baseline compat). Without the code in front of me (and an hour of my time, I bet) I can't answer this question for myself, but I suspect it is being done for a reason, but I'm not certain why and it seems too magical to me. As stated above, it should at least be thoroughly documented, particularly the reasoning behind why. |
Also @sunnysideup where you using the |
Hi @patricknelson - yes, I was using the
I hear what you are saying about a single transaction. Maybe the core devs will tell me - dont waste your time with Mysql, because all of this is handled by PostGreSQL perfectly. Then it is simply a matter of updating the docs.... |
I had a look at all the PDOConnector.php and searched for |
For now, I'd start with adding a warning in the docs about wrapping any I'd be careful with adding a transaction around the two write calls: Depending on the context, this could lead to lock escalations (from record to page to table), and cause deadlocks. Also, not clear how nested transactions work across the different database drivers. Ideally we'd get away with a single SQL insert/update statement. It'll be an API change, since Anyway, I don't think this is high on the agenda for core devs, but worth solving by the community in a pull request :) |
regarding the unique index issue, i ran into the problem as well. It doesn't affect any nullable type (you can have as many null unique column as you want) but it becomes really annoying for non nullable types (in my case : integers). Since SilverStripe doesn't provide null ints out of the box (which is really sad, because it makes it impossible to distinguish actual 0 from non initialized values, but that's another topic), I ended up storing numbers in varchars which can be null. so maybe a way to easily prevent problems with unique constraints is to simply make sure the column can be null? |
Hi Ingo and Koala for your comments, That all makes a lot of sense. I would recommend that someone update the docs as it would save people the hassle and frustration of trying out unique indexes and then having to carefully debug and discover the above. Something like this:
|
This is probably the most natural solution to the problem, and the least likely to introduce regressions. This could possibly be done in a semver way, making it safe to do in 3.x. This would need to be tested for regressions against extensions which implement augmentWrite, however, so more investigation is needed. :) If it turns out to be hard, we can do it in 4.0. |
I just ran into this in 4.1.2. ChangeSetItem has a unique index which breaks in writeBaseRecord() due to the index being initially broken due to duplicate records. There's a race condition where if you are publishing multiple items, you will temporarily end up with |
I'd like our IDs to change to be UUIDs so that we don't rely on the DB to assign IDs for us. But this would be a rather large change that would be for 5.x |
I'm thinking just writing the full base table row up front would probably be a better default than leaving them to db default :P |
Finally fixed this with #8831 |
Fixed with #8831 |
Summary:
Unique indexes are not such a good idea within the current Silverstripe set up (3.5.3), AFAIK, because they are prone to race conditions and other errors, making all future inserts impossible. The reason for this is that
DataObject::write
carries out its writing in two steps. The first step writes an empty record to obtain an ID, the second (and further writes), add the data. If the first write is performed and the second write crashes or whatever else can go wrong then the table can not be written to ever again (until the empty record is manually removed).When creating a unique index, you are likely to encounter problems due to the way SS inserts new records. Please forgive me if I got it all wrong. I have limited time to raise this ticket and I am not coding pro, but it may be worth noting. I have already raised this on the dev group (https://groups.google.com/forum/#!topic/silverstripe-dev/n-FZ8pSsWD8)
AFAIK Records (database rows) are written,
This means that no records can be written to a table while any write process is between 1 and 2. Sometimes this can be because of race-like conditions but it could also be the case that 2 does not happen because of faulty data, server failure, or whatever else you can imagine.
Here is the relevant code (from 3.5.3) in DataObject:
Line 1313 ... 1316 above executes a query:
call to DB::prepared_query:
and then to the connector:
The way I read this code is that
writeBaseRecord
actually inserts an empty record, after which further write(s) add the additional data.Options (some of them a little crazy!):
The text was updated successfully, but these errors were encountered: