Skip to content

Commit

Permalink
Do a quick check of database tables on master backend and mythtv-setu…
Browse files Browse the repository at this point in the history
…p startup.

This change causes the master mythbackend and mythtv-setup to check the database tables at application startup.  In the event one or more tables is not OK (crashed or errored), it will attempt to repair the tables.  If the repair succeeds, startup will continue.  Otherwise, the application will exit with a message indicating that the user should repair the database tables.

The quick check should pick up crashed tables caused by improperly closed data files.  Some classes of data errors won't be detected (checksum errors or incorrect links), but this should handle the majority of issues caused by crashed/killed mysqld server and full filesystems.  Note, also, that clients (mythfrontend, mythavtest, ...) and remote backends will not check database tables, since it is not unusual for them to be started and restarted many times while the master backend continues to run.


git-svn-id: http://svn.mythtv.org/svn/trunk@25426 7dbf422c-18fa-0310-86e9-fd20926502f2
  • Loading branch information
sphery committed Jul 26, 2010
1 parent e50e2a7 commit 278b7ca
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 2 deletions.
148 changes: 148 additions & 0 deletions mythtv/libs/libmyth/dbutil.cpp
Expand Up @@ -10,6 +10,7 @@
#include <QRegExp>
#include <QDateTime>
#include <QSqlError>
#include <QSqlRecord>
#include <QProcess>

#include "dbutil.h"
Expand Down Expand Up @@ -262,6 +263,153 @@ MythDBBackupStatus DBUtil::BackupDB(QString &filename)
return kDB_Backup_Failed;
}

/** \fn DBUtil::CheckTables(const bool repair, const QString options)
* \brief Checks database tables
*
* This function will check database tables.
*
* \param repair Repair any tables whose status is not OK
* \param options Options to be passed to CHECK TABLE; defaults to QUICK
* \return false if any tables have status other than OK; if repair is true,
* returns true if those tables were repaired successfully
* \sa DBUtil::RepairTables(const QStringList)
*/
bool DBUtil::CheckTables(const bool repair, const QString options)
{
MSqlQuery query(MSqlQuery::InitCon());
if (!query.isConnected())
return false;

const QStringList all_tables = GetTables();

if (all_tables.empty())
return true;

QString sql = QString("CHECK TABLE %1 %2;").arg(all_tables.join(", "))
.arg(options);

VERBOSE(VB_IMPORTANT, "Checking database tables.");
if (!query.exec(sql))
{
MythDB::DBError("DBUtil Checking Tables", query);
return false;
}

QStringList tables = CheckRepairStatus(query);
bool result = true;
if (!tables.empty())
{
VERBOSE(VB_IMPORTANT, QString("Found crashed database table(s): %1")
.arg(tables.join(", ")));
if (repair == true)
// If RepairTables() repairs the crashed tables, return true
result = RepairTables(tables);
else
result = false;
}

return result;
}

/** \fn DBUtil::RepairTables(const QStringList &tables)
* \brief Repairs database tables
*
* This function will repair MyISAM database tables.
*
* Care should be taken in calling this function. It should only be called
* when no clients are accessing the database, and in the event the MySQL
* server crashes, it is critical that a REPAIR TABLE is run on the table
* that was being processed at the time of the server crash before any other
* operations are performed on that table, or the table may be destroyed. It
* is up to the caller of this function to guarantee the safety of performing
* database repairs.
*
* \param tables List of tables to repair
* \return false if errors were encountered repairing tables
* \sa DBUtil::CheckTables(const bool, const QString)
*/
bool DBUtil::RepairTables(const QStringList &tables)
{
MSqlQuery query(MSqlQuery::InitCon());
if (!query.isConnected())
return false;

QString all_tables = tables.join(", ");
VERBOSE(VB_IMPORTANT, QString("Repairing database tables: %1")
.arg(all_tables));

QString sql = QString("REPAIR TABLE %1;").arg(all_tables);
if (!query.exec(sql))
{
MythDB::DBError("DBUtil Repairing Tables", query);
return false;
}

QStringList bad_tables = CheckRepairStatus(query);
bool result = true;
if (!bad_tables.empty())
{
VERBOSE(VB_IMPORTANT, QString("Unable to repair crashed table(s): %1")
.arg(bad_tables.join(", ")));
result = false;
}
return result;
}

/** \fn DBUtil::CheckRepairStatus(MSqlQuery &query)
* \brief Parse the results of a CHECK TABLE or REPAIR TABLE run.
*
* This function reads the records returned by a CHECK TABLE or REPAIR TABLE
* run and determines the status of the table(s). The query should have
* columns Table, Msg_type, and Msg_text.
*
* The function properly handles multiple records for a single table. If the
* last record for a given table shows a status (Msg_type) of OK (Msg_text),
* the table is considered OK, even if an error or warning appeared before
* (this could be the case, for example, when an empty table is crashed).
*
* \param query An already-executed CHECK TABLE or REPAIR TABLE query whose
* results should be parsed.
* \return A list of names of not-OK (errored or crashed) tables
* \sa DBUtil::CheckTables(const bool, const QString)
* \sa DBUtil::RepairTables(const QStringList)
*/
QStringList DBUtil::CheckRepairStatus(MSqlQuery &query)
{
QStringList tables;
QSqlRecord record = query.record();
int table_index = record.indexOf("Table");
int type_index = record.indexOf("Msg_type");
int text_index = record.indexOf("Msg_text");
QString table, type, text, previous_table;
bool ok = true;
while (query.next())
{
table = query.value(table_index).toString();
type = query.value(type_index).toString();
text = query.value(text_index).toString();
if (table != previous_table)
{
if (!ok)
{
tables.append(previous_table);
ok = true;
}
previous_table = table;
}
// If the final row shows status OK, the table is now good
if ("status" == type.toLower() && "ok" == text.toLower())
ok = true;
else if ("error" == type.toLower() ||
("status" == type.toLower() && "ok" != text.toLower()))
ok = false;
}
// Check the last table in the list
if (!ok)
tables.append(table);
return tables;
}

/** \fn DBUtil::GetTables(void)
* \brief Retrieves a list of tables from the database.
*
Expand Down
4 changes: 4 additions & 0 deletions mythtv/libs/libmyth/dbutil.h
Expand Up @@ -37,6 +37,9 @@ class MPUBLIC DBUtil
int CompareDBMSVersion(int major, int minor=0, int point=0);

MythDBBackupStatus BackupDB(QString &filename);
static bool CheckTables(const bool repair = false,
const QString options = "QUICK");
static bool RepairTables(const QStringList &tables);

static bool IsNewDatabase(void);
static bool IsBackupInProgress(void);
Expand All @@ -57,6 +60,7 @@ class MPUBLIC DBUtil
bool ParseDBMSVersion(void);

static QStringList GetTables(void);
static QStringList CheckRepairStatus(MSqlQuery &query);

QString CreateBackupFilename(QString prefix = "mythconverg",
QString extension = ".sql");
Expand Down
1 change: 1 addition & 0 deletions mythtv/libs/libmythdb/exitcodes.h
Expand Up @@ -44,6 +44,7 @@
#define BACKEND_EXIT_NO_MYTHCONTEXT GENERIC_EXIT_NO_MYTHCONTEXT
#define BACKEND_EXIT_INVALID_CMDLINE GENERIC_EXIT_INVALID_CMDLINE
#define BACKEND_EXIT_DB_OUTOFDATE GENERIC_EXIT_DB_OUTOFDATE
#define BACKEND_EXIT_DB_ERROR GENERIC_EXIT_DB_ERROR
#define BACKEND_EXIT_INVALID_TIMEZONE GENERIC_EXIT_INVALID_TIMEZONE
#define BACKEND_EXIT_OPENING_LOGFILE_ERROR GENERIC_EXIT_OPENING_LOGFILE_ERROR
#define BACKEND_EXIT_NO_CONNECT GENERIC_EXIT_START-1
Expand Down
15 changes: 13 additions & 2 deletions mythtv/programs/mythbackend/main_helpers.cpp
Expand Up @@ -51,6 +51,7 @@
#include "mythsystemevent.h"
#include "main_helpers.h"
#include "backendcontext.h"
#include "dbutil.h"

#include "mediaserver.h"
#include "httpstatus.h"
Expand Down Expand Up @@ -738,6 +739,18 @@ int run_backend(const MythCommandLineParser &cmdline)
if (!setup_context(cmdline))
return BACKEND_EXIT_NO_MYTHCONTEXT;

bool ismaster = gCoreContext->IsMasterHost();

if (ismaster)
{
if (!DBUtil::CheckTables(true))
{
VERBOSE(VB_IMPORTANT, "Error checking database tables. Please "
"run optimize_mythdb.pl or mysqlcheck --repair.");;
return BACKEND_EXIT_DB_ERROR;
}
}

if (!UpgradeTVDatabaseSchema(true, true))
{
VERBOSE(VB_IMPORTANT, "Couldn't upgrade database to new schema");
Expand All @@ -746,8 +759,6 @@ int run_backend(const MythCommandLineParser &cmdline)

///////////////////////////////////////////

bool ismaster = gCoreContext->IsMasterHost();

g_pUPnp = new MediaServer(ismaster, !cmdline.IsUPnPEnabled() );

if (!ismaster)
Expand Down
8 changes: 8 additions & 0 deletions mythtv/programs/mythtv-setup/main.cpp
Expand Up @@ -33,6 +33,7 @@
#include "startprompt.h"
#include "mythsystemevent.h"
#include "expertsettingseditor.h"
#include "dbutil.h"

using namespace std;

Expand Down Expand Up @@ -592,6 +593,13 @@ int main(int argc, char *argv[])
LanguageSettings::prompt();
LanguageSettings::load("mythfrontend");

if (!DBUtil::CheckTables(true))
{
VERBOSE(VB_IMPORTANT, "Error checking database tables. Please "
"run optimize_mythdb.pl or mysqlcheck --repair.");;
return BACKEND_EXIT_DB_ERROR;
}

if (!UpgradeTVDatabaseSchema(true))
{
VERBOSE(VB_IMPORTANT, "Couldn't upgrade database to new schema.");
Expand Down

0 comments on commit 278b7ca

Please sign in to comment.