mirror of
https://github.com/mangosfour/server.git
synced 2025-12-15 01:37:00 +00:00
[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:
parent
9bc37afa28
commit
631ce36680
19 changed files with 655 additions and 547 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue