Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
- The MySQL MyISAM database driver defines a maximum key length of 1000 bytes. This corresponds to 333 UTF-8 characters of three bytes each.
- We have had several issues where patches go in that break MyISAM tables by declaring longer keys. See, for example:
Proposed resolution
- Enforce a maximum key length of 333 characters.
- Throw a typed exception if a module declares a schema with a key that is longer than 333 characters.
- Implement this in
Drupal\Core\Database\Schema
. Despite that it's a MySQL limitation, as chx put it, "writing a non-mysql compliant database schema should be frown upon" and "all database drivers are equal but some are more equal than others."
Remaining tasks
The attached patch adds a class constant and a typed exception. Still to do:
- Actually throw the exception.
- Add test coverage.
- Create a change notification.
- Update http://drupal.org/node/146843 to document the limitation.
API changes
- The constant
Drupal\Core\Database\Schema::MAX_KEY_LENGTH
is added. - Database table keys added by
hook_schema()
are explicitly limited to 333 total characters.
Comment | File | Size | Author |
---|---|---|---|
#91 | database-schema-1852896-91.patch | 8.24 KB | pk188 |
#89 | database-schema-1852896-89.patch | 8.87 KB | pk188 |
#76 | interdiff.txt | 692 bytes | andypost |
#75 | database-schema-1852896-75.patch | 8.79 KB | puregin |
#75 | interdiff-69-75.txt | 230 bytes | puregin |
Comments
Comment #1
xjmComment #2
xjmComment #3
chx CreditAttribution: chx commentedAlso note that the comments needs to say MyISAM incompatible, not MySQL incompatible, MySQL is a somewhat thin layer around its storage engines. You can include really crazy things as MySQL storage engines, like http://blog.mariadb.org/announcing-the-cassandra-storage-engine/ . So it's the storage engine that has limits like this not MySQL. And while Drupal defaults to InnoDB to avoid a complicated discussion on whether a table is best as InnoDB or MyISAM, the latter still has its uses -- having deadlocks is just one of the horrific tolls you pay for the ACID compliance of InnoDB.
In short: yes, enforce 333 because it's the lower common denominator and do not make Drupal MyISAM incompatible.
Comment #4
chx CreditAttribution: chx commentedTo be perfectly honest we do not necessarily need to restrict the column length themselves, we could even revert the quickfix I filed for the user data patch -- we could just use the length in the index. Something like array(uid, array(module, 128), array(name, 128)) and have name for example become a TEXT even. Yea that would be better.
Comment #5
sunCalculating the total length of indexes (taking explicitly specified limits into account but otherwise falling back on the column schema definitions) and throwing an exception makes sense to me.
However, no need to revert the user.data fix, as we still need to limit the module name length throughout Drupal core, since that has additional, other consequences.
Comment #6
chx CreditAttribution: chx commentedmaking name a TEXT in userdata however would make sense given it comes from a PHP array which had no limitations...
Comment #7
sun{users_data}.value is a blob already. .name is limited to 128 chars, which should be sufficient for all use-cases. If there is something longer, then that's what the upgrade path is for.
Comment #8
chx CreditAttribution: chx commentedThe upgrade path is going to break if the original blob contained two array keys equal at the first 128 characters:
see.
Comment #9
pillarsdotnet CreditAttribution: pillarsdotnet commentedDoesn't this issue violate the general mantra of "Drupal doesn't babysit broken code"?
And also the mantra of "Drupal is database-agnostic" ?
Comment #10
pillarsdotnet CreditAttribution: pillarsdotnet commentedAlso, these search strings match exactly one page: this one.
Comment #11
pillarsdotnet CreditAttribution: pillarsdotnet commentedComment #12
chx CreditAttribution: chx commentedYes, those were quotes only because I said them on IRC.
There's a certain difference between a call that gives you a fatal on some log page which can be fixed by deleting a single line from the database and a Drupal that simply does not install on certain system which is what this issue is about.
Yes we are database agnostic which means that we really to write against the lowest common denominator.
Of course, as I mentioned above, an exception might not be necessary. We might just add something to the MySQL driver to enforce a summary key length of 1000, for example, by enforcing a max 64 character index column length on VARCHAR if the total character number is above 333.
Comment #13
xjmThrowing typed exceptions is not babysitting broken code. It's the opposite of babysitting broken code. It's preventing regressions from being introduced repeatedly and senselessly. See the issues in the summary.
Comment #14
sunI'd rather object to db-driver specific magic. At least I don't see how a machine can do informed decisions on which parts of an index can be shortened and which cannot. That's something the application developer/DBA has to define based on the business logic.
I actually don't see what's wrong with throwing an exception early and unconditionally. If that means that developers are not able to install or update a module or even entire Drupal at all, and they immediately get to know that they need to fix stuff, that's positive in my book.
Comment #15
chx CreditAttribution: chx commentedSure I am fine with an exception too. Just wanted to say, we do not need to shorten the DB columns themselves -- we can shorten the index only which IMO is not something we typically did before.
Comment #16
sunAnother one: #1871032: Taxonomy module fails to install on MyISAM due to too long schema index
So here's an initial proposed table index validation implementation.
The validation is only possible when creating a new table, since we need access to the full table definition. Therefore, the validation cannot run when adding new fields, changing fields, or adding individual indexes.
Comment #18
sunSorry.
Comment #20
chx CreditAttribution: chx commentedThis is not why the patch fails but +class SchemaIndexLengthException extends SchemaException implements DatabaseException { } the implements is not necessary, http://api.drupal.org/api/drupal/core!lib!Drupal!Core!Database!SchemaExc... already implements DatabaseException . Also, throw new SchemaIndexLengthException(t('Table index @name is too long. Found @length characters (exceeds maximum of @max).', array( i think we format_string in exception, it's not useful to translate those.
Comment #21
sunYeah, the actual reason is that the Standard profile fails to install with:
:) — which means that we need to fix #1871032: Taxonomy module fails to install on MyISAM due to too long schema index first. I already proposed a fix over there.
Comment #22
andypostAlso we need a help page to describe this because there's a different ways to fix index length
1) recompile mysql with bigger limit
2) make a truncated index like #1871032-11: Taxonomy module fails to install on MyISAM due to too long schema index
Comment #23
xjm#18: schema.index_.18.patch queued for re-testing.
Comment #25
xjmI don't this work with the way the indexes are defined in #1871032-25: Taxonomy module fails to install on MyISAM due to too long schema index. Does it?
I think exceptions are not supposed to be translated? #817160: Don't translate exceptions
Comment #26
sunIt could probably use a comment, but yes, this works - the else condition retrieves the trimmed/limited length for the column from the key definition:
Comment #27
xjmOh! Clever. I see now.
Here's some comments, plus switching
t()
toformat_string()
. Turns out the array format for the index isn't really documented anywhere (that I could find) other than the ancient Schema API handbook page... The parameter documentation onSchema::addIndex()
and friends could probably be updated to explain the two possible formats for the index fields.I also rolled the patch together with #1871032: Taxonomy module fails to install on MyISAM due to too long schema index to confirm that it will work once that issue is committed.
Comment #28
BerdirThis is just a re-upload of database-schema-1852896-27-do-not-test.patch so that it can be tested now.
Comment #29
xjmSo, the value we are actually concerned about is the total number of bytes (< 1000), and it'd depend on the size of the int, which is implementation-specific. However, we want to throw the exception based on the size of the index specifically on MyISAM... hmmm.
Edit: See http://drupal.org/node/159605 .
Comment #30
BerdirI guess we need to check the other databases if there are similar limitations. But I think throwing an exception right there is given for something like this, where we know that it is not going to work for one of the supported backends and there is no way around it.
Comment #31
xjmWell, I checked all the core-supported databases when I filed the issue, and MyISAM was the smallest by far. Details:
InnoDB: 3072 bytes http://dev.mysql.com/doc/refman/5.0/en/innodb-restrictions.html
Postgres: Seems to be configurable, but defaults to 2700ish? http://wiki.postgresql.org/wiki/FAQ#What_is_the_maximum_size_for_a_row.2..., http://stackoverflow.com/questions/4539443/postgresql-primary-key-length...
SQLite: Limits are configurable; no limit on index length outside of the maximum number of columns in the index (default 2000) and string length (default 1 billion) http://www.sqlite.org/limits.html
Googling this morning, I did discover some lower ones for databases not supported by core:
Oracle: Depends on block size; 758 with a 2K block http://www.dba-oracle.com/t_ora_01450_maximum_key_length_exceeded.htm
SQL Server: 900 bytes http://msdn.microsoft.com/en-us/library/ms191241(v=sql.105).aspx
However, I don't think we should worry about those on the schema level since core doesn't support them anyway.
Comment #32
xjmSo, how do we go about counting the MyISAM size of the non-text fields in the limit? Do we hardcode the MySQL key size of each and calculate the total number of bytes? That seems gross.
From http://drupal.org/node/159605 and createFieldSql(), the field types that don't have a length appear to be the numeric types, and BLOBs. If you are trying to put an index on a BLOB you deserve what you get. From the table, the biggest the numeric types get is 8 B (the equivalent of up to 3 characters), except
numeric
:http://dev.mysql.com/doc/refman/5.1/en//storage-requirements.html
So if there's a limit of 65 digits, and 4 bytes per nine digits, that's a maximum index size of 29 B for a numeric field, or (rounding up) the equivalent of ten characters.
Comment #33
xjmWe could just allot
numeric
a length of 10 "characters" in a constant, and any other numeric type a length of three "characters," and call that good. I don't know if we need to throw an exception that says "Don't put indexes on BLOBs."Comment #34
tstoecklerWell, both SQL Server and Oracle have pretty sophisticated drivers in contrib. I can see that it might not make sense to directly support that in core, but maybe we could at least make this easy to override for them. I'm not familiar enough with the DB system to know if one can override e.g. the \Drupal\Core\Database\Schema class (which the patch above modifies) as a contrib driver.
At the very least we should ping Damien Tournoud and aaaristo about this. It would be sad to see this nice in-depth research go to waste and then have them figure this out on their own when someone files a bug over there.
Comment #35
Berdir@tstoeckler: The Schema class can and is extended by by all drivers, create table statements and so on are not really standardized anyway.
Also, this is not about driver-specific validation. That's not really necessary, if it will blow up for the driver that you are using then, well, it will blow up and you can fix it. This is validation for making sure that it works for all supported backends even if you are not using it yourself. And core has a hard enough time to maintain the supported backends (just check how many PostgreSQL 6.x -> 7.x uprade bugs are still open).
Making it a validation that prevents the installation of modules has the advantage over a test that it will also validate all contrib modules, as they are written.
Comment #36
tstoecklerYes, that's how I understood the patch. I was assuming that we don't want to go the route of supporting Oracle and SQL Server directly in core (which would limit us to 260 characters if I'm not mistaken, per @xjm's findings). But if Schema is overridden anyway, those contrib backends can easily adjust Schema::MAX_INDEX_LENGTH that is introduced here and get the full benefit of the validation of this patch. Of course, they wouldn't have the full treatment of all (contrib) modules directly being validated, but still.
Comment #37
andypostPostgresql have only 2 limits:
#1148856: Postgres schema doesn't support keylength on a unique index
and wiki
Comment #38
xjmThis has happened one too many times. See #1969680: Installation fails with MyISAM - key too long.
#32 summarizes what this is stuck on. Feedback please. :)
Comment #39
xjmMaybe we need to count MyISAM bytes rather than characters, and use 1000 as the max "byte" length or whatever.
Comment #40
Berdir#28: database-schema-1852896-28.patch queued for re-testing.
Comment #41
BerdirRe-tested to confirm that it should now correctly fail.
Comment #43
chx CreditAttribution: chx commentedLet's not forget this for updates as well. #1976110: D8 upgrade path: file module; Update #8001 was reported.
Comment #44
xjmSo, ugh. In order to take into account an arbitrary number of arbitrary-type fields added to the index, we essentially have to count how many bytes it would be in MySQL, and validate based on that.
Comment #45
xjmOops, need that format_string().
Comment #46
xjm=/
Comment #47
xjmWith a test. I'll also try to write some unit tests for various table structures.
Comment #49
xjmDerp.
Comment #51
xjmCouple dumb bugs in there. I've got it working locally now... and it definitely works...
http://drupalcode.org/project/drupal.git/commitdiff/e83535c1c844da096ce4... reduced the index to exactly bytes (with an int at 4 bytes, while we're rounding up to 8). So either we can't punt on the numeric sizes by rounding up to 8, or we need to reduce the length of that field by another character. (Which should happen anyway in #1852454: Restrict module and theme name lengths to 50 characters.)
Comment #52
xjmA more specific allotment for the numeric types, and a workaround for #1969680: Installation fails with MyISAM - key too long to allow the patch to be tested.
Aside, the thing where batch API just hangs and doesn't write fatals to the log is a pain. Anyone know of an issue for that?
Comment #53
xjmComment #55
Berdir#52: database-schema-1852896-52-PASS.patch queued for re-testing.
Comment #57
BerdirHere's a re-roll.
My suggestion would be to get this in asap. We can always improve for other database backends, but it's main purpose is to notify us before we break this again and make @patrickd_drupal's life miserable :) That's not fair considering he's given us simplytest.me ;)
Comment #59
catchA MyISAM test bot would also solve this #697220: Run tests on all supported database servers. Downgrading to normal - I don't consider working around Drupal.org infrastructure a critical task.
Comment #60
BerdirThe advantage of having this as a core check means that it is detected much earlier and also for contrib and custom modules. If you wait until the testbot breaks, then even for core, it was already committed and you possibly need to think about upgrade changes and so on.
Comment #61
xjmRight, it's still something we can fix, but we don't need to make it a top priority if we can do a better job of catching environment-specific regressions. This fix would catch only one particular such regression. :)
Comment #62
xjmComment #63
xjmComment #64
puregin CreditAttribution: puregin commentedRerolled patch from #57
Comment #65
disasm CreditAttribution: disasm commentedComment #66
puregin CreditAttribution: puregin commentedForgot to attach my interdiff for the previous patchfile.
This fixes a couple of bugs in
testSchemaMaxIndexSize()
inSchemaTest.php.
First, the size of an Int in a MySQL table is 4 bytes, not 8, so the size calculations
were off. I corrected the test by adding an additional int field to the test table
and key.
Also fixed the name of the variable referencing the test table.
Finally, corrected the messages reported by the tests themselves.
The tests all pass for me now, BUT there is now a DatabaseException thrown:
This appears to be due to MySQL constraints on the primary key.
Comment #67
puregin CreditAttribution: puregin commentedArgh. I fat-fingered the status, setting back to needs review.
Comment #69
puregin CreditAttribution: puregin commentedOK, I think I finally have this right. Sorry for the confusion. Comment #66 still applies.
Comment #71
xjmComment #72
alexpottConsidering that the primary goal of this issue is to ensure that Drupal 8.x HEAD continues to work on MyISAM I think we should just move this to a test that:
Comment #73
xjmSo #72 would resolve the problem for core, but not for contrib. We could repurpose the code in
Schema::indexSize()
for such a test.Comment #74
puregin CreditAttribution: puregin commentedChanging the type of the two text fields used for keys from
text
tovarchar
makes the test pass.I suppose this raises the question of whether there should be a test for primary keys containing text/blob fields.
Comment #75
puregin CreditAttribution: puregin commentedNew patch with two line change as suggested in #74.
Comment #76
andypostI'd like to mention here that we have upgrade path broken for now - all upgrade tests are failed in myIsam environment
Failed: Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 1000 bytes: ALTER TABLE {file_usage} CHANGE `id` `id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'The primary key of the object using the file.'; Array ( ) in Drupal\Core\Database\Connection->query() (line 571 of ..../core/lib/Drupal/Core/Database/Connection.php).
A quick hack to allow pass tests attached.
Comment #76.0
andypostUpdated issue summary.
Comment #77
andypostComment #78
danblack CreditAttribution: danblack commented75: database-schema-1852896-75.patch queued for re-testing.
Comment #79
xjmThis happened again in #2270917: Limit collection name due to MyISAM restrictions and test ConfigCollectionInfo class.
Comment #82
alexpottWe need to re-look at the key length limit since we no longer support MyISAM (#2275535: [policy, no patch] Drop support for non-transactional database engines (including MyISAM)). InnoDB has a different max length and I'm not sure about postgres or SQLite.
Comment #83
xjmAt this point I would just suggest wontfixing this issue.
Comment #84
danblack CreditAttribution: danblack commentedPrefixes can be up to 767 bytes long for InnoDB tables or 3072 bytes if the innodb_large_prefix option is enabled however innodb_large_index was only introduced in 5.5.14.
Sqlite appears to have no limits http://www.sqlite.org/lang_createindex.html
Postgres appears to have no limits: http://www.postgresql.org/docs/9.1/static/sql-createindex.html
Comment #86
geerlingguy CreditAttribution: geerlingguy as a volunteer commentedIs there actually a
innodb_large_index
configuration? I can't seem to find any documentation for it, and I'm hitting the errorSpecified key was too long; max key length is 1000 bytes
when trying to convert tables to utf8mb4 (see #2762599: PDOException while converting cache_block table). It seems the problem is an index on thefilter_format
table.[Edit: Note that the problem I was encountering was due to trying to convert a really old MyISAM-ridden database to UTF8mb4; after converting its tables to InnoDB everything worked fine.]
Comment #89
pk188 CreditAttribution: pk188 at OpenSense Labs commentedRe rolled. With last few lines not changed.
Comment #91
pk188 CreditAttribution: pk188 at OpenSense Labs commentedRe rolled.