2011-01-25 3 views
2

У меня есть приложение реального времени, которое обрабатывает информацию и записывает ее в базу данных MySQL (на самом деле MariaDB, fork of MySQL). Он составляет около 1,5 миллионов вставок в день + 150 000 удалений.MySQL Опимость вставных последовательностей

У меня возникли проблемы с производительностью и я не знаю, как заставить его функционировать лучше.

Основная структура приложения заключается в том, что у меня есть класс-производитель, который подталкивает Struct к потоковому детексу. Следующий код

#include "dbUserQueue.h" 


dbUserQueue::~dbUserQueue() { 
} 

void dbUserQueue::createConnection() 
{ 
    sql::Driver * driver = sql::mysql::get_driver_instance(); 
    std::auto_ptr<sql::Connection> newCon(driver->connect(dbURL, dbUser, dbPass)); 
    con = newCon; 
    std::auto_ptr<sql::Statement> stmt(con->createStatement()); 
    stmt->execute("USE twitter"); 
} 

inline void dbUserQueue::updateStatement(const std::string & value, 
        std::auto_ptr< sql::PreparedStatement> & stmt, const int index) 
{ 
    if(value != "\0") stmt->setString(index, value); 
    else stmt->setNull(index,sql::DataType::VARCHAR); 
} 

inline void dbUserQueue::updateStatement(const boost::int64_t & value, 
        std::auto_ptr< sql::PreparedStatement> & stmt, const int index) 
{ 
    if(value != -1) stmt->setInt64(index,value); 
    else stmt->setNull(index,sql::DataType::BIGINT); 
} 

inline void dbUserQueue::updateStatement(const bool value, 
        std::auto_ptr< sql::PreparedStatement> & stmt, const int index) 
{ 
    stmt->setBoolean(index, value); 
} 

inline void dbUserQueue::updateStatement(const int value, 
        std::auto_ptr< sql::PreparedStatement> & stmt, const int index) 
{ 
    if(value != -1) stmt->setInt(index,value); 
    else stmt->setNull(index,sql::DataType::INTEGER); 
} 

inline void dbUserQueue::updateStatementDateTime(const std::string & value, 
        std::auto_ptr< sql::PreparedStatement> & stmt, const int & index) 
{ 
    if(value != "\0") stmt->setDateTime(index, value); 
    else stmt->setNull(index,sql::DataType::DATE); 
} 

/* 
* This method creates a database connection 
* and then creates a new thread to process the incoming queue 
*/ 
void dbUserQueue::start() { 
    createConnection(); 
    if(con->isClosed() == false) 
    { 
     insertStmt = std::auto_ptr< sql::PreparedStatement>(con->prepareStatement("\ 
insert ignore into users(contributors_enabled, created_at, \ 
description, favourites_count, followers_count, \ 
following, friends_count, geo_enabled, id, lang, listed_count, location, \ 
name, notifications, screen_name, show_all_inline_media, statuses_count, \ 
url, utc_offset, verified) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)")); 
    } 
    thread = boost::thread(&dbUserQueue::processLoop, this); 
} 

/* 
* Stops the thread once it is finished processing the information 
*/ 
void dbUserQueue::join(){ 
    thread.interrupt(); 
    thread.join(); 
} 

/* 
* The worker function of the thread. 
* Pops items from the queue and updates the database accordingly. 
*/ 
void dbUserQueue::processLoop() { 
    user input; 
    int recordCount = 0; 
    con->setAutoCommit(false); 
    while (true) { 
     try { 
      if(recordCount >= 1000) 
      { 
       recordCount = 0; 
       con->commit(); 
      } 
      // Insert all the data into the prepared statement 
      if (userQ.wait_and_pop(input)) { 
       updateStatement(input.contributors_enabled, insertStmt, 1); 
       updateStatementDateTime(input.created_at, insertStmt, 2); 
       updateStatement(input.description, insertStmt, 3); 
       updateStatement(input.favourites_count, insertStmt, 4); 
       updateStatement(input.followers_count, insertStmt, 5); 
       updateStatement(input.following, insertStmt, 6); 
       updateStatement(input.friends_count, insertStmt, 7); 
       updateStatement(input.geo_enabled, insertStmt, 8); 
       updateStatement(input.id, insertStmt, 9); 
       updateStatement(input.lang, insertStmt, 10); 
       updateStatement(input.listed_count, insertStmt, 11); 
       updateStatement(input.location, insertStmt, 12); 
       updateStatement(input.name, insertStmt, 13); 
       updateStatement(input.notifications, insertStmt, 14); 
       updateStatement(input.screenName, insertStmt, 15); 
       updateStatement(input.show_all_inline_media, insertStmt, 16); 
       updateStatement(input.statuses_count, insertStmt, 17); 
       updateStatement(input.url, insertStmt, 18); 
       updateStatement(input.utc_offset, insertStmt, 19); 
       updateStatement(input.verified, insertStmt, 20); 

       insertStmt->executeUpdate(); 
       insertStmt->clearParameters(); 
       recordCount++; 
       continue; 
      } 

     } catch (std::exception & e) { 
     } 
    }// end of while 

    // Close the statements and the connection before exiting 
    insertStmt->close(); 
    con->commit(); 
    if(con->isClosed() == false) 
     con->close(); 
} 

Мои вопросы касательно того, как улучшить производительность? Вещи, я попытался:
Наличие нескольких потребителей при подключении к одной MySQL/MariaDB
Совершение после большого количества записей

Single Producer, Single consumer, commit after 1000 records = ~275 Seconds 
Dual Producer, Triple consumers, commit after 1000 records = ~100 Seconds 
Dual Producer, Triple consumers, commit after 2000 records = ~100 Seconds 
Dual Producer, Triple consumers, commit every 1 record = ~100 Seconds 
Dual Producer, 6 Consumers, commit every 1 record = ~95 Seconds 
Dual Producer, 6 Consumers, commit every 2000 records = ~100 Seconds 
Triple Producer, 6 Consumesr, commit every 2000 records = ~100 Seconds 

Пара заметок на проблемной области. Сообщения для вставки и удаления появляются случайным образом в течение дня со средним значением ~ 20 вставок/удалений в секунду, всплески намного выше, но нет причин, по которым обновления не могут быть поставлены в очередь на короткий период, если очередь не растет до большого.

Таблица, в которую в настоящее время вставляются данные, содержит около 52 миллионов записей. Вот информация таблицы MySQL

CREATE TABLE `users` (
    `id` bigint(20) unsigned NOT NULL, 
    `contributors_enabled` tinyint(4) DEFAULT '0', 
    `created_at` datetime NOT NULL, 
    `description` varchar(255) DEFAULT NULL, 
    `favourites_count` int(11) NOT NULL, 
    `followers_count` int(11) DEFAULT NULL, 
    `following` varchar(255) DEFAULT NULL, 
    `friends_count` int(11) NOT NULL, 
    `geo_enabled` tinyint(4) DEFAULT '0', 
    `lang` varchar(255) DEFAULT NULL, 
    `listed_count` int(11) DEFAULT NULL, 
    `location` varchar(255) DEFAULT NULL, 
    `name` varchar(255) DEFAULT NULL, 
    `notifications` varchar(45) DEFAULT NULL, 
    `screen_name` varchar(45) NOT NULL, 
    `show_all_inline_media` tinyint(4) DEFAULT NULL, 
    `statuses_count` int(11) NOT NULL, 
    `url` varchar(255) DEFAULT NULL, 
    `utc_offset` int(11) DEFAULT NULL, 
    `verified` tinyint(4) DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    UNIQUE KEY `id_UNIQUE` (`id`) 
) ENGINE=MARIA DEFAULT CHARSET=latin1 CHECKSUM=1 PAGE_CHECKSUM=1 TRANSACTIONAL=1 
+0

Я думаю, что вы разместили неправильную структуру таблицы. Вы разместили «твиты», но вы вставляете «пользователей». –

+0

@ Larry: Спасибо за улов. У меня проблема с обеими частями, вставка твитов и вставка пользователей. Я просто разместил один, так как код очень похож. –

+4

Ну, сразу же вы указали один и тот же столбец дважды. PRIMARY KEY (id) даст UNIQUE INDEX (id). –

ответ

1

Вы можете изменить код, чтобы делать объемные вставки, а не вставлять одну строку за раз.

+0

Как вы предлагаете это делать? Перечисление нескольких значений в инструкции insert. У вас есть подготовленный оператор, который сразу вставил 100 записей или вы бы построили строку SQL «на лету» и просто выполнили ее каждые 100 записей? (Я только случайно выбрал 100, это может быть любое число). Я попытался использовать транзакции и подготовленное заявление, поэтому с моим [ограниченным] пониманием, которое должно помочь смягчить некоторые из этих проблем, поскольку оно фактически не обновляет базу данных до тех пор, пока я не совершу, сделав ее только 1 записью и с подготовленным оператором I уменьшите пропускную способность, просто передав переменные. –

+1

Я не делал этого с подготовленными операторами, но генерировал инструкцию insert в виде строки и передавал строку mysqlpp :: Query. Я ожидаю, что вы можете использовать подготовленное заявление для массовых вставок аналогичным образом. Если вы делаете инструкцию insert для каждой строки, я думаю, что между клиентом и сервером намного больше трафика, что должно замедлить работу. –

+0

Итак, вы предлагаете подготовленный оператор с заданным количеством полей «значений». Что-то вроде этого: PrepareStatement ("INSERT INTO tbl_name (a, b, c) VALUES (?,?,?), (?,?,?), (?,?,?)") ;, и присвоение значений для несколько записей в один оператор, а затем его выполнение. Я считаю это очень «неуклюжим» и чувствую, что это не оптимальное решение. Я попробую и посмотрю, увеличит ли производительность и вернется к вам. Было бы неплохо, если бы был способ автоматизировать вставку нескольких записей. –

Смежные вопросы