[11045] Rewrite internals of DB layer. Simplify code and use less locking. Spawn and use separate connections for sync and async DB requests. Implement database connection pool for SELECT queries. Up to maximum 16 connections supported. Disable 'autocommit' mode for MySQL.

UPDATE YOUR CONFIGS!

Defaults:
LoginDatabaseConnections = 1
WorldDatabaseConnections = 1
CharacterDatabaseConnections = 1

If you are not using <mtmaps> patch do not change the default settings - this is useless. You can try following option in your MySQL config to squeeze even more performance from your DB:

[mysqld]
transaction-isolation = READ-COMMITTED

Great thanks to Undergarun, kero99 and selector for making tests and providing very useful feedback and DB statistics! Have fun :)

Signed-off-by: Ambal <pogrebniak@gala.net>
This commit is contained in:
Ambal 2011-01-19 22:04:54 +02:00
parent 9bc37afa28
commit 631ce36680
19 changed files with 655 additions and 547 deletions

View file

@ -23,10 +23,10 @@
#include "Platform/Define.h"
#include "Threading.h"
#include "DatabaseEnv.h"
#include "Database/MySQLDelayThread.h"
#include "Database/SqlOperations.h"
#include "Timer.h"
size_t DatabaseMysql::db_count = 0;
void DatabaseMysql::ThreadStart()
{
mysql_thread_init();
@ -37,9 +37,7 @@ void DatabaseMysql::ThreadEnd()
mysql_thread_end();
}
size_t DatabaseMysql::db_count = 0;
DatabaseMysql::DatabaseMysql() : Database(), mMysql(0)
DatabaseMysql::DatabaseMysql()
{
// before first connection
if( db_count++ == 0 )
@ -61,31 +59,40 @@ DatabaseMysql::~DatabaseMysql()
if (m_delayThread)
HaltDelayThread();
if (mMysql)
mysql_close(mMysql);
//destroy SqlConnection objects
if(m_pQueryConnections.size())
{
for (int i = 0; i < m_pQueryConnections.size(); ++i)
delete m_pQueryConnections[i];
m_pQueryConnections.clear();
}
if(m_pAsyncConn)
{
delete m_pAsyncConn;
m_pAsyncConn = NULL;
}
//Free Mysql library pointers for last ~DB
if(--db_count == 0)
mysql_library_end();
}
bool DatabaseMysql::Initialize(const char *infoString)
SqlConnection * DatabaseMysql::CreateConnection()
{
return new MySQLConnection();
}
bool MySQLConnection::Initialize(const char *infoString)
{
if(!Database::Initialize(infoString))
return false;
tranThread = NULL;
MYSQL *mysqlInit;
mysqlInit = mysql_init(NULL);
MYSQL * mysqlInit = mysql_init(NULL);
if (!mysqlInit)
{
sLog.outError( "Could not initialize Mysql connection" );
return false;
}
InitDelayThread();
Tokens tokens = StrSplit(infoString, ";");
Tokens::iterator iter;
@ -108,7 +115,7 @@ bool DatabaseMysql::Initialize(const char *infoString)
database = *iter++;
mysql_options(mysqlInit,MYSQL_SET_CHARSET_NAME,"utf8");
#ifdef WIN32
#ifdef WIN32
if(host==".") // named pipe use option (Windows)
{
unsigned int opt = MYSQL_PROTOCOL_PIPE;
@ -121,7 +128,7 @@ bool DatabaseMysql::Initialize(const char *infoString)
port = atoi(port_or_socket.c_str());
unix_socket = 0;
}
#else
#else
if(host==".") // socket use option (Unix/Linux)
{
unsigned int opt = MYSQL_PROTOCOL_SOCKET;
@ -135,7 +142,7 @@ bool DatabaseMysql::Initialize(const char *infoString)
port = atoi(port_or_socket.c_str());
unix_socket = 0;
}
#endif
#endif
mMysql = mysql_real_connect(mysqlInit, host.c_str(), user.c_str(),
password.c_str(), database.c_str(), port, unix_socket, 0);
@ -155,16 +162,19 @@ bool DatabaseMysql::Initialize(const char *infoString)
// This is wrong since mangos use transactions,
// autocommit is turned of during it.
// Setting it to on makes atomic updates work
if (!mysql_autocommit(mMysql, 1))
DETAIL_LOG("AUTOCOMMIT SUCCESSFULLY SET TO 1");
// ---
// if you want atomic updates to work - USE TRANSACTIONS!!!
// no need to mess up with autocommit mode which might degrade server performance!
if (!mysql_autocommit(mMysql, 0))
DETAIL_LOG("AUTOCOMMIT SUCCESSFULLY SET TO 0");
else
DETAIL_LOG("AUTOCOMMIT NOT SET TO 1");
DETAIL_LOG("AUTOCOMMIT NOT SET TO 0");
/*-------------------------------------*/
// set connection properties to UTF8 to properly handle locales for different
// server configs - core sends data in UTF8, so MySQL must expect UTF8 too
PExecute("SET NAMES `utf8`");
PExecute("SET CHARACTER SET `utf8`");
Execute("SET NAMES `utf8`");
Execute("SET CHARACTER SET `utf8`");
return true;
}
@ -177,33 +187,27 @@ bool DatabaseMysql::Initialize(const char *infoString)
}
}
bool DatabaseMysql::_Query(const char *sql, MYSQL_RES **pResult, MYSQL_FIELD **pFields, uint64* pRowCount, uint32* pFieldCount)
bool MySQLConnection::_Query(const char *sql, MYSQL_RES **pResult, MYSQL_FIELD **pFields, uint64* pRowCount, uint32* pFieldCount)
{
if (!mMysql)
return 0;
uint32 _s = WorldTimer::getMSTime();
if(mysql_query(mMysql, sql))
{
// guarded block for thread-safe mySQL request
ACE_Guard<ACE_Thread_Mutex> query_connection_guard(mMutex);
uint32 _s = WorldTimer::getMSTime();
if(mysql_query(mMysql, sql))
{
sLog.outErrorDb( "SQL: %s", sql );
sLog.outErrorDb("query ERROR: %s", mysql_error(mMysql));
return false;
}
else
{
DEBUG_FILTER_LOG(LOG_FILTER_SQL_TEXT, "[%u ms] SQL: %s", WorldTimer::getMSTimeDiff(_s,WorldTimer::getMSTime()), sql );
}
*pResult = mysql_store_result(mMysql);
*pRowCount = mysql_affected_rows(mMysql);
*pFieldCount = mysql_field_count(mMysql);
// end guarded block
sLog.outErrorDb( "SQL: %s", sql );
sLog.outErrorDb("query ERROR: %s", mysql_error(mMysql));
return false;
}
else
{
DEBUG_FILTER_LOG(LOG_FILTER_SQL_TEXT, "[%u ms] SQL: %s", WorldTimer::getMSTimeDiff(_s,WorldTimer::getMSTime()), sql );
}
*pResult = mysql_store_result(mMysql);
*pRowCount = mysql_affected_rows(mMysql);
*pFieldCount = mysql_field_count(mMysql);
if (!*pResult )
return false;
@ -218,7 +222,7 @@ bool DatabaseMysql::_Query(const char *sql, MYSQL_RES **pResult, MYSQL_FIELD **p
return true;
}
QueryResult* DatabaseMysql::Query(const char *sql)
QueryResult* MySQLConnection::Query(const char *sql)
{
MYSQL_RES *result = NULL;
MYSQL_FIELD *fields = NULL;
@ -231,11 +235,10 @@ QueryResult* DatabaseMysql::Query(const char *sql)
QueryResultMysql *queryResult = new QueryResultMysql(result, fields, rowCount, fieldCount);
queryResult->NextRow();
return queryResult;
}
QueryNamedResult* DatabaseMysql::QueryNamed(const char *sql)
QueryNamedResult* MySQLConnection::QueryNamed(const char *sql)
{
MYSQL_RES *result = NULL;
MYSQL_FIELD *fields = NULL;
@ -252,44 +255,15 @@ QueryNamedResult* DatabaseMysql::QueryNamed(const char *sql)
QueryResultMysql *queryResult = new QueryResultMysql(result, fields, rowCount, fieldCount);
queryResult->NextRow();
return new QueryNamedResult(queryResult,names);
}
bool DatabaseMysql::Execute(const char *sql)
{
if (!mMysql)
return false;
// don't use queued execution if it has not been initialized
if (!m_threadBody) return DirectExecute(sql);
ACE_Guard<ACE_Thread_Mutex> _lock(nMutex);
tranThread = ACE_Based::Thread::current(); // owner of this transaction
TransactionQueues::iterator i = m_tranQueues.find(tranThread);
if (i != m_tranQueues.end() && i->second != NULL)
{ // Statement for transaction
i->second->DelayExecute(sql);
}
else
{
// Simple sql statement
m_threadBody->Delay(new SqlStatement(sql));
}
return true;
}
bool DatabaseMysql::DirectExecute(const char* sql)
bool MySQLConnection::Execute(const char* sql)
{
if (!mMysql)
return false;
{
// guarded block for thread-safe mySQL request
ACE_Guard<ACE_Thread_Mutex> query_connection_guard(mMutex);
uint32 _s = WorldTimer::getMSTime();
if(mysql_query(mMysql, sql))
@ -308,7 +282,7 @@ bool DatabaseMysql::DirectExecute(const char* sql)
return true;
}
bool DatabaseMysql::_TransactionCmd(const char *sql)
bool MySQLConnection::_TransactionCmd(const char *sql)
{
if (mysql_query(mMysql, sql))
{
@ -323,100 +297,22 @@ bool DatabaseMysql::_TransactionCmd(const char *sql)
return true;
}
bool DatabaseMysql::BeginTransaction()
bool MySQLConnection::BeginTransaction()
{
if (!mMysql)
return false;
// don't use queued execution if it has not been initialized
if (!m_threadBody)
{
if (tranThread == ACE_Based::Thread::current())
return false; // huh? this thread already started transaction
mMutex.acquire();
if (!_TransactionCmd("START TRANSACTION"))
{
mMutex.release(); // can't start transaction
return false;
}
return true; // transaction started
}
ACE_Guard<ACE_Thread_Mutex> _lock(nMutex);
tranThread = ACE_Based::Thread::current(); // owner of this transaction
TransactionQueues::iterator i = m_tranQueues.find(tranThread);
if (i != m_tranQueues.end() && i->second != NULL)
// If for thread exists queue and also contains transaction
// delete that transaction (not allow trans in trans)
delete i->second;
m_tranQueues[tranThread] = new SqlTransaction();
return true;
return _TransactionCmd("START TRANSACTION");
}
bool DatabaseMysql::CommitTransaction()
bool MySQLConnection::CommitTransaction()
{
if (!mMysql)
return false;
// don't use queued execution if it has not been initialized
if (!m_threadBody)
{
if (tranThread != ACE_Based::Thread::current())
return false;
bool _res = _TransactionCmd("COMMIT");
tranThread = NULL;
mMutex.release();
return _res;
}
ACE_Guard<ACE_Thread_Mutex> _lock(nMutex);
tranThread = ACE_Based::Thread::current();
TransactionQueues::iterator i = m_tranQueues.find(tranThread);
if (i != m_tranQueues.end() && i->second != NULL)
{
m_threadBody->Delay(i->second);
m_tranQueues.erase(i);
return true;
}
return false;
return _TransactionCmd("COMMIT");
}
bool DatabaseMysql::RollbackTransaction()
bool MySQLConnection::RollbackTransaction()
{
if (!mMysql)
return false;
// don't use queued execution if it has not been initialized
if (!m_threadBody)
{
if (tranThread != ACE_Based::Thread::current())
return false;
bool _res = _TransactionCmd("ROLLBACK");
tranThread = NULL;
mMutex.release();
return _res;
}
ACE_Guard<ACE_Thread_Mutex> _lock(nMutex);
tranThread = ACE_Based::Thread::current();
TransactionQueues::iterator i = m_tranQueues.find(tranThread);
if (i != m_tranQueues.end() && i->second != NULL)
{
delete i->second;
m_tranQueues.erase(i);
}
return true;
return _TransactionCmd("ROLLBACK");
}
unsigned long DatabaseMysql::escape_string(char *to, const char *from, unsigned long length)
unsigned long MySQLConnection::escape_string(char *to, const char *from, unsigned long length)
{
if (!mMysql || !to || !from || !length)
return 0;
@ -424,23 +320,4 @@ unsigned long DatabaseMysql::escape_string(char *to, const char *from, unsigned
return(mysql_real_escape_string(mMysql, to, from, length));
}
void DatabaseMysql::InitDelayThread()
{
assert(!m_delayThread);
//New delay thread for delay execute
m_threadBody = new MySQLDelayThread(this); // will deleted at m_delayThread delete
m_delayThread = new ACE_Based::Thread(m_threadBody);
}
void DatabaseMysql::HaltDelayThread()
{
if (!m_threadBody || !m_delayThread) return;
m_threadBody->Stop(); //Stop event
m_delayThread->wait(); //Wait for flush to DB
delete m_delayThread; //This also deletes m_threadBody
m_delayThread = NULL;
m_threadBody = NULL;
}
#endif