Problem/Motivation
MySQL 8 has been released and includes performance enhancements. It would be nice to integrate those enhancements with Drupal.
https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-0.html
Proposed resolution
Test a variety of Drupalp 8 distributions running with MySQL 8 and fix bugs as they appear.
Instructions to test patch using docker
You can get mysql 8 using docker and set it up for Drupal testing.
docker run --name mysql8 -p 127.0.0.1:3307:3306 -e MYSQL_DATABASE=drupal -e MYSQL_ROOT_PASSWORD=dev -d mysql:latest --default-authentication-plugin=mysql_native_password
Then you can run tests like so:
SIMPLETEST_DB="mysql://root:dev@127.0.0.1:3307/drupal" sudo -u _www ./vendor/bin/phpunit -v -c core core/tests/Drupal/KernelTests/Core/Cache/ChainedFastBackendTest.php
NB. replace _www which whichever user is running your webserver.
Remaining tasks
- core/lib/Drupal/Core/Database/Driver/mysql/Connection.php (183): Remove NO_AUTO_CREATE_USER
- core/lib/Drupal/Core/Database/Schema.php (202): change "table_name" to "TABLE_NAME as table_name"
- Update documentation on Drupal.org listing MySQL 8 as supported.
User interface changes
- Note during the site install that MySQL 8 is supported.
API changes
N/A
Data model changes
N/A
Comments
Comment #2
blakemorgan commentedComment #3
blakemorgan commentedComment #4
cilefen commentedComment #5
cilefen commented@blakemorgan: Please open and link an issue in https://www.drupal.org/project/drupalci to add MySQL 8 testing.
Comment #6
blakemorgan commented@cilefen Done. Is there anything else I can work on to help with this?
Comment #7
f1mishutka commentedAny chances for backport 'NO_AUTO_CREATE_USER' removal to Drupal 8.5?
Comment #8
f1mishutka commentedComment #9
cilefen commentedComment #10
almaudoh commentedAdded two more things to the patch so that KernelTests may pass:
SELECT table_name FROM information_schema.tables WHEREtoSELECT table_name as table_name FROM information_schema.tables WHERESELECT column_comment FROM information_schema.columns WHEREtoSELECT column_comment as column_comment FROM information_schema.columns WHERESELECT table_comment FROM information_schema.tables WHEREtoSELECT table_comment as table_comment FROM information_schema.tables WHEREComment #11
andypost@almaudoh any reason for this "as" I can't find anything about it in https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-0.html
Comment #12
almaudoh commentedSo for the
$results = $this->connection->query("SELECT table_name as table_name FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments());$resultsstdobject will not havetable_nameproperty, but rather will haveTABLE_NAMEso the test for$table->table_namewill always fail.Comment #13
gappleI wanted to better understand NO_AUTO_CREATE_USER and what the effect of removing it would be.
It was added in MySQL 5.5, and both enabled by default and deprecated in MySQL 5.7.
Doing a quick search through Drupal core I did not find any GRANT statements, so I don't think this change should affect Drupal.
Comment #14
mmjvb commentedUnless MySQL 8 is going to be the minimum requirement for D8.6, I object to unconditionally removing NO_AUTO_CREATE_USER from sql-mode. Removing it for MySQL 5.7 and below should have its own issue.
Comment #15
gappleI added the version check conditional from the D7 patch in order to only remove NO_AUTO_CREATE_USER when connecting to MySQL 8+
Comment #16
alexpottWe have a mysql 8 test bot. I've run the tests against #15. Looks like we have a long way to go :(
Comment #17
amateescu commentedThis big condition is quite hard to read, can we remove
NO_AUTO_CREATE_USERfrom the initial list of sql modes and only add it conditionally if the mysql version is <8.0.11?Comment #18
alexpottJust added a way to test using docker so people don't wreck their local environment (like I did :) )
Comment #19
alexpottComment #20
alexpottThe problem with
ChainedFastBackendTestis caused by \Drupal\Core\Cache\DatabaseBackend::schemaDefinition()If you comment out
'binary' => TRUE,tests against mysql 5.7 fail in exactly the same way.So I guess how you define binary strings has changed.
Comment #21
alexpottOn MYSQL 8 the create column definition looks like:
And mysql 5.7 it looks like:
With the patch attached it looks like this:
It looks like in HEAD the instructions to be BINARY and COLLATE ascii_general_ci are contradicting and on MySQL 8 it takes the last and on 5.7 it takes the first. That was fun to work out.
Comment #22
alexpottA little bit more progress.
Comment #23
alexpottI don't like this fix. It's because of the simpletest table which has a column called
function.I think we might consider renaming the column.
Comment #24
jibranAre we going to provide the upgrade path for existing site moving from MySQL 5.5/5.6/5.7 to 8? Is it possible to migrate from one MySQL version to another in these cases?
Comment #25
alexpottLast fix we can do. Now we need @mixologic to fix the new MySQL 8 container to have enough disk space since it appears that we run out half way through functional tests.
re #24 - fortunately upgrading databases is not our job. And yes MySQL provides an upgrade path from MySQL 5.7 to MySQL 8. We just need to be able to support MySQL 5.5, 5.6, 5.7 and 8.
One thought I have is that
this check will be interesting for MariaDB which is already version 10.
Comment #26
alexpottSo the latest MariaDB version string is
5.5.5-10.3.8-MariaDB-1:10.3.8+maria~jessie:DSo when doing
version_compare($version_server, '8.0.11', '<')on that it'll use the5.5.5so all is fine there. TheNO_AUTO_CREATE_USERwill still be added. That said I'm not looking forward to the day the MySQL and MariaDB diverge properly.Atm we can't increase
past 5.5.5 because that'd drop MariaDB support completely. lolz.
Comment #27
alexpottI've added #2985530: Add documentation note about MySQL minimum version and MariaDB to document the version string thing so we don't drop support for MariaDB by mistake.
Comment #28
alexpottLet's see if it is the binary logs that are causing the disk space problem.
Comment #29
effulgentsia commentedWell that's a shame. According to https://mariadb.com/kb/en/library/mariadb-vs-mysql-compatibility/, MariaDB 10.2 and 10.3 are said to be more similar to MySQL 5.7 than 5.5.
In #2983639-4: [PP-1] Figure out a way to not join the workspace_association table for every duplicate base table join of an entity query, I pointed out that MySQL 5.5 is EOL in 5 months. And causing us problems in that issue. So figuring out if we want to and how we can raise MySQL requirements to 5.6 while still supporting MariaDB will soon become timely, but could be done in a separate issue.
Comment #30
MixologicRe-uploading because 22/25 hit one of those obscure drupal.org bugs that come up when multiple tabs and old forms conflict with things.
Comment #31
alexpottLooks like we have a random fail in
Drupal\Tests\views\Kernel\Entity\RowEntityRenderersTestsee https://www.drupal.org/pift-ci-job/1017101Comment #32
Mixologicout of curiosity, trying that one test many bunches of times.
Comment #33
alexpottThinking some more about the new MySQL reserved words like
function. I think we need to make\Drupal\Core\Database\Driver\mysql\Connection::escapeField()work more like\Drupal\Core\Database\Driver\pgsql\Connection::escapeField(). The patch attached will escape fields and aliases as necessary.Let's see what breaks.
Comment #35
alexpottTurns out statically caching the quoted version is a bad idea because
$this->escapedNameson the Connection class has both field and table names together.I did that because originally the MySQL escapeField() did a more Postgres like escapeField() - using regular expressions etc... but I don't think that that is necessary. Interestingly I think the Postgres escaping could be simplified hugely by taking a similar approach and always escaping. There's not much point in checking for a reserved word list or case as it does now - it could just always quote and be done. Less complexity and more reliability.
Comment #36
alexpottAh we also escape field names that are empty strings :)
Comment #39
amateescu commentedThe previous code was not really correct, because if someone had a different collation than
utf(mb4)_general_ci, the resulting character set would've been wrong.I suggest we do an
explode()on _ and use the first element of the array.Why not use
$this->version()?We need to do the same in
\Drupal\Core\Database\Driver\mysql\Upsert.Also, is there any reason for using
$finstead of$field?Why use plural here when there is only one record?
Also, there is a comment in
\Drupal\Core\Database\Driver\mysql\Connection::open()which is no longer accurate:Comment #40
alexpottThanks for the review @amateescu.
1. Fixed.
2. Because the method we're in is static.
3. Yep -
$fis copy&paste4. Fixed
And addressed the comments too.
Comment #41
alexpottJust somethings I noticed whilst fixing the test failures in #36 with the patch in #40.
In HEAD this does something very funny. It add a condition to the query like
11 = ""which as it turns out has the same effect as doing1 <> 1.This change should make this funkiness much more robust with alternate DB drivers.
Comment #42
amateescu commentedI usually like to write "always false" queries with
1 = 0, I think it's a bit more easy to read, but I don't feel that strongly about it if you prefer1 <> 1:)Comment #43
alexpott@amateescu I don't feel strongly about
1 = 0or1 <> 1- i just left it as1 <> 1because that was the way it was written.Comment #44
alexpottSo the big question for me is do we want the extra strictness of properly quoting column and aliases on MySQL.
On one hand we get better compatibility across MySQL 8 and 5.x (and probably MariaDB) since we no longer need to fear any mysql-compatible implementation adding new reserved words like
function. We also get stricter adherence to our own API (for example, the1 <> 1thing above) and our code will probably work better with contrib / custom drivers because we have to call the escaping functions instead of making assumptions based on MySQL's norms.On the other hand it is highly likely that these changes will have an impact on contrib and custom code. Any test that makes assertions against the string version of a query is likely to break (not that this is a good test) and some code that works today like
->condition('1 <> 1')will break (however as stated above this is not really using the API as intended).The middle ground would be add a dictionary of reserved words for MySQL to quote if the field or alias matches. This would preserve API oddities like
->condition('1 <> 1')but also allow us to deal with column names likefunction. What we'd lose from this approach is the stricter usage of core API which forces code like\Drupal\user\Plugin\EntityReferenceSelection\UserSelection::entityQueryAlterto escape fields as expected.Comment #45
catchThese two potential breakages seem OK in a minor release, but it's maybe not great to introduce that change just before an alpha, rather than earlier in the minor cycle when contrib would have had more time to catch up. It does seem quite unlikely people are writing code like that though, and a lot simpler than adding a dictionary.
Comment #46
amateescu commentedI think that's a good middle ground, and it's exactly what the pgsql driver is doing as well:
\Drupal\Core\Database\Driver\pgsql\Connection::doEscapeComment #47
alexpottDiscussed a bit more with @catch and @amateescu on slack. We agreed to go for the dictionary approach because quoting everywhere will be disruptive for 8.6.x and MySQL 8 support is important. We'll file a followup to discuss the advantages / disadvantages of quoting everywhere. I'll also file fixes for API misuses this issue already found.
Bumping to major bug as not supporting the latest stable for MySQL feels very buggy.
Comment #48
amateescu commentedOpened #2986334: Add a way to enforce an empty result set for a database condition query for the
->condition('1 <> 1')API misuse found in this patch :)Comment #49
alexpottSo I've go the list of reserved words by selecting out of a MySQL 8 DB.
No reserved words were removed since 5.5 see:
So this is the complete list of MySQL reserved words. Postgres formats the list all on one line. Imo 1 word per line is better because it makes it easier to review changes.
I've removed all the changes that were due to quoting always as these are unnecessary now and hopefully that means we can land this in 8.6.x
Also added test case for upserting with special column names.
Comment #50
alexpottHere's a related older issue about MySQL and reserved keywords #2560965: Reserved column names are broken on MySQL, the test lies
Comment #51
alexpottOh and this will fix the column name part of #371: resolve ANSI SQL-92/99/2003 reserved words conflict in query statements
One thought is do we need to handle table names too? Someone might have a table called
groupsfor example. (Another new reserved word in MySQL 8).Comment #53
alexpottCrediting @bojanz for the work on #2560965: Reserved column names are broken on MySQL, the test lies which I've marked as a duplicate.
Comment #54
alexpottHandling tables is going to be awful - we have to somehow handle
\Drupal\Core\Database\Connection::setPrefix()and per-table prefixes. Ugh. At least per-table prefixes are deprecated in Drupal 9 as per #2551549: Deprecate per-table prefixingComment #55
alexpottI've opened #2986452: Database reserved keywords need to be quoted as per the ANSI standard to discuss quoting of keywords in general. What a mess.
Comment #57
amateescu commentedI don't think we need to worry about prefixes,
escapeTable()is supposed to be called on the non-prefixed table name, so let's be consistent with the pgsql implementation and overrideescapeTable()as well.We should probably rename
quoteField()todoEscape(), also for consistency reasons. And replace $field with $string everywhere :)Comment #58
alexpott@amateescu I'd rather discuss
escapeTable()in a follow-up. If we make that change for mysql table prefixes for tables with reserved names will be broken here's why:functionand the database prefix is set to "test12345678_".select count(*) from {function}select count(*) from {"function"}select count(*) from test12345678_"function"Therefore doing the quoting in
escapeTable()is dangerous.I suspect that the current Postgres
escapeTable()is broken for table prefixes too and if it every did actually quote something it'd be highly likely to break other things. We just don't have any tables in core called a reserved word. In my opinion we should proceed with #49 because it allows MySQL 8 to work without making anything worse and do a more comprehensive solution in #2986452: Database reserved keywords need to be quoted as per the ANSI standard for identifier escaping.Comment #59
alexpott\Drupal\Core\Database\Driver\pgsql\Connection::doEscape()is really poorly named. From what I've seen, the db driver documentation use the language of quoting identifiers, why not use the same language? And by that argument I think I should update the patch to match that language.Comment #60
amateescu commentedI like the change in #59 a lot and based on #58 it is indeed better to handle escaping tables in a followup.
Comment #61
alexpottOpened #2986539: \Drupal\user\Plugin\EntityReferenceSelection\UserSelection::entityQueryAlter() should escape the fake condition column on replacement to handle an issue found by previous versions of this patch.
Comment #63
alexpottRerolled.
Comment #65
alexpottMistakenly reverted an unrelated change (it was related at one point in the patch's history).
Comment #66
catchCommitting this now prior to the beta, we don't want to be eight+ months behind MySQL versions.
Let's continue in #2986452: Database reserved keywords need to be quoted as per the ANSI standard which is a more comprehensive fix.
Committed/pushed to 8.7.x and cherry-picked to 8.6.x. Thanks!
Comment #69
damienmckennaGreat work everyone!
BTW does anyone have performance numbers to compare against 5.6 or 5.7?
Comment #71
gábor hojtsyComment #72
ayesh commentedComment #73
cilefen commented#3089902: "Azure Database for MySQL server" reports wrong database version
Comment #74
drupal_simply_amazing commented@damienmckenna its increase speed 60 to 70 percent. Our content has too many paragraphs fields. On MySQL 5.7 it loads 28 to 31seconds after changing to MySQL 8 it is now loading at 11 to 14.4 sec.
Comment #75
andy_read commentedAttempting to use migrate_upgrade with the source D7 DB on MySQL 8 I get the error "Source database does not contain a recognizable Drupal version" (as described at https://www.drupal.org/project/migrate_upgrade/issues/2637870#comment-13...). I haven't got to the bottom of this, but my strong suspicion is that the system table is not correctly escaped with backticks. I see some escaping of reserved keywords for tables in the pgsql connection class, but not mysql. The mysql connection class lists the version 8 reserved words, but does not seem to use them for escaping table names. I also see no sign of backtick escaping.
Comment #76
alexpott@Andy_Read see #2986452: Database reserved keywords need to be quoted as per the ANSI standard for a generic solution to escaping all the things.
Comment #77
jcl324Comment #78
effulgentsia commentedLooks like this was the issue that introduced the decision to use a double quote rather than a backtick.
Meanwhile, for Drupal 7, #2978575: Mysql 8 Support on Drupal 7 chose to default to a backtick, but make that overridable in settings.php with
$conf['mysql_identifier_quote_character'].See #3218978: MySQL driver allows settings.php to remove ANSI_QUOTES from sql_mode, but doesn't work when it is for if/how we want to fix things for when the MySQL sql_mode doesn't include ANSI_QUOTES.