diff --git a/admin/index.php b/admin/index.php index b50c0e17a9c..409b53118c9 100644 --- a/admin/index.php +++ b/admin/index.php @@ -373,6 +373,12 @@ if ($showbloglevelupgrade) { echo $OUTPUT->box(get_string('bloglevelupgradenotice', 'admin'), 'generalbox adminwarning'); } +// diagnose DB, especially the sloppy MyISAM tables +$diagnose = $DB->diagnose(); +if ($diagnose !== NULL) { + echo $OUTPUT->box($diagnose, 'generalbox adminwarning'); +} + // Alert if we are currently in maintenance mode if (!empty($CFG->maintenance_enabled)) { echo $OUTPUT->box(get_string('sitemaintenancewarning2', 'admin', "$CFG->wwwroot/$CFG->admin/settings.php?section=maintenancemode"), 'generalbox adminwarning'); diff --git a/lang/en/error.php b/lang/en/error.php index cd9e0003ac1..40903ff2758 100755 --- a/lang/en/error.php +++ b/lang/en/error.php @@ -341,6 +341,7 @@ $string['multiplerecordsfound'] = 'Multiple records found, only one record expec $string['multiplerestorenotallow'] = 'Multiple restore execution not allowed!'; $string['mustbeloggedin'] = 'You must be logged in to do this'; $string['mustbeteacher'] = 'You must be a teacher to look at this page'; +$string['myisamproblem'] = 'Database tables are using MyISAM database engine, it is recommended to use ACID compliant engine with full transaction support such as InnoDB.'; $string['needcopy'] = 'You need to copy something first!'; $string['needcoursecategroyid'] = 'Either course id or category must be specified'; $string['needphpext'] = 'You need to add {$a} support to your PHP installation'; diff --git a/lib/ddl/mysql_sql_generator.php b/lib/ddl/mysql_sql_generator.php index d0a4d7d02dc..d4f8b82e900 100644 --- a/lib/ddl/mysql_sql_generator.php +++ b/lib/ddl/mysql_sql_generator.php @@ -96,6 +96,33 @@ class mysql_sql_generator extends sql_generator { return array("ALTER TABLE $this->prefix$tablename AUTO_INCREMENT = $value"); } + /** + * Given one correct xmldb_table, returns the SQL statements + * to create it (inside one array) + */ + public function getCreateTableSQL($xmldb_table) { + // first find out if want some special db engine + $engine = null; + if (method_exists($this->mdb, 'get_dbengine')) { + $engine = $this->mdb->get_dbengine(); + } + + $sqlarr = parent::getCreateTableSQL($xmldb_table); + + if (!$engine) { + // we rely on database defaults + return $sqlarr; + } + + // let's inject the engine into SQL + foreach ($sqlarr as $i=>$sql) { + if (strpos($sql, 'CREATE TABLE ') === 0) { + $sqlarr[$i] .= " ENGINE = $engine"; + } + } + + return $sqlarr; + } /** * Given one correct xmldb_table, returns the SQL statements @@ -103,7 +130,7 @@ class mysql_sql_generator extends sql_generator { */ public function getCreateTempTableSQL($xmldb_table) { $this->temptables->add_temptable($xmldb_table->getName()); - $sqlarr = $this->getCreateTableSQL($xmldb_table); + $sqlarr = parent::getCreateTableSQL($xmldb_table); // we do not want the engine hack included in create table SQL $sqlarr = preg_replace('/^CREATE TABLE (.*)/s', 'CREATE TEMPORARY TABLE $1', $sqlarr); return $sqlarr; } diff --git a/lib/dml/moodle_database.php b/lib/dml/moodle_database.php index 371cb751211..979e9d10e56 100644 --- a/lib/dml/moodle_database.php +++ b/lib/dml/moodle_database.php @@ -236,6 +236,16 @@ abstract class moodle_database { return $cfg; } + /** + * Diagnose database and tables, this function is used + * to verify database and driver settings, db engine types, etc. + * + * @return string null means everything ok, string means problem found. + */ + public function diagnose() { + return null; + } + /** * Connect to db * Must be called before other methods. diff --git a/lib/dml/mysqli_native_moodle_database.php b/lib/dml/mysqli_native_moodle_database.php index 6e6dcfa0a8d..6a0ff48a355 100644 --- a/lib/dml/mysqli_native_moodle_database.php +++ b/lib/dml/mysqli_native_moodle_database.php @@ -116,6 +116,71 @@ class mysqli_native_moodle_database extends moodle_database { return 'native'; } + /** + * Returns the current MySQL db engine. + * + * This is an ugly workaround for MySQL default engine problems, + * Moodle is designed to work best on ACID compliant databases + * with full transaction support. Do not use MyISAM. + * + * @return string or null MySQL engine name + */ + public function get_dbengine() { + if (isset($this->dboptions['dbengine'])) { + return $this->dboptions['dbengine']; + } + + $engine = null; + + if (!$this->external) { + // look for current engine of our config table (the first table that gets created), + // so that we create all tables with the same engine + $sql = "SELECT engine FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE() AND table_name = '{$this->prefix}config'"; + $this->query_start($sql, NULL, SQL_QUERY_AUX); + $result = $this->mysqli->query($sql); + $this->query_end($result); + if ($rec = $result->fetch_assoc()) { + $engine = $rec['engine']; + } + $result->close(); + } + + if ($engine) { + return $engine; + } + + // get the default database engine + $sql = "SELECT @@storage_engine"; + $this->query_start($sql, NULL, SQL_QUERY_AUX); + $result = $this->mysqli->query($sql); + $this->query_end($result); + if ($rec = $result->fetch_assoc()) { + $engine = $rec['@@storage_engine']; + } + $result->close(); + + if (!$this->external and $engine === 'MyISAM') { + // we really do not want MyISAM for Moodle, InnoDB or XtraDB is a reasonable defaults if supported + $sql = "SHOW STORAGE ENGINES"; + $this->query_start($sql, NULL, SQL_QUERY_AUX); + $result = $this->mysqli->query($sql); + $this->query_end($result); + while ($res = $result->fetch_assoc()) { + if ($res['Engine'] === 'InnoDB' and $res['Support'] === 'YES') { + $engine = 'InnoDB'; + break; + } + if ($res['Engine'] === 'XtraDB' and $res['Support'] === 'YES') { + $engine = 'XtraDB'; + break; + } + } + $result->close(); + } + + return $engine; + } + /** * Returns localised database type name * Note: can be used before connect() @@ -143,6 +208,36 @@ class mysqli_native_moodle_database extends moodle_database { return get_string('databasesettingssub_mysqli', 'install'); } + /** + * Diagnose database and tables, this function is used + * to verify database and driver settings, db engine types, etc. + * + * @return string null means everything ok, string means problem found. + */ + public function diagnose() { + $sloppymyisamfound = false; + $prefix = str_replace('_', '\\_', $this->prefix); + $sql = "SHOW TABLE STATUS WHERE Name LIKE BINARY '$prefix%'"; + $this->query_start($sql, null, SQL_QUERY_AUX); + $result = $this->mysqli->query($sql); + $this->query_end($result); + if ($result) { + while ($arr = $result->fetch_assoc()) { + if ($arr['Engine'] === 'MyISAM') { + $sloppymyisamfound = true; + break; + } + } + $result->close(); + } + + if ($sloppymyisamfound) { + return get_string('myisamproblem', 'error'); + } else { + return null; + } + } + /** * Connect to db * Must be called before other methods. @@ -1021,6 +1116,8 @@ class mysqli_native_moodle_database extends moodle_database { * * MyISAM does not support support transactions. * + * You can override this via the dbtransactions option. + * * @return bool */ protected function transactions_supported() { @@ -1028,20 +1125,20 @@ class mysqli_native_moodle_database extends moodle_database { return $this->transactions_supported; } - // Only will accept transactions if using InnoDB storage engine (more engines can be added easily BDB, Falcon...) + // this is all just guessing, might be better to just specify it in config.php + if (isset($this->dboptions['dbtransactions'])) { + $this->transactions_supported = $this->dboptions['dbtransactions']; + return $this->transactions_supported; + } + $this->transactions_supported = false; - $sql = "SELECT @@storage_engine"; - $this->query_start($sql, NULL, SQL_QUERY_AUX); - $result = $this->mysqli->query($sql); - $this->query_end($result); + $engine = $this->get_dbengine(); - if ($rec = $result->fetch_assoc()) { - if (in_array($rec['@@storage_engine'], array('InnoDB'))) { - $this->transactions_supported = true; - } + // Only will accept transactions if using compatible storage engine (more engines can be added easily BDB, Falcon...) + if (in_array($engine, array('InnoDB', 'INNOBASE', 'BDB', 'XtraDB', 'Aria', 'Falcon'))) { + $this->transactions_supported = true; } - $result->close(); return $this->transactions_supported; }