Улучшение производительности `Update` (проблема блокировки строк) - PullRequest
1 голос
/ 20 декабря 2011

Я запускаю 30 скриптов (PHP CLI) в Linux, каждый скрипт обновляет (зацикливает) данные в базе данных MySQL.

Когда я набрал '1003 *' в терминале, я вижу, что многие строки заблокированы на 10-30 секунд. В основном это Update очереди. Как улучшить производительность быстрее? Я использую движок InnoDB.

PHP-скрипт выглядит примерно так:

//status and process are indexed.
$SQL = "SELECT * FROM data WHERE status = 0 AND process = '1'";
$query = $db->prepare($SQL);
$query->execute();

//about 100,000+ rows for each script
while ($row = $query->fetch(PDO::FETCH_ASSOC)) {
        checking($row);
        sleep(2);
}

function checking($data) {

  $error = errorCheck($data['number']);

  if ($error) {
     //number indexed
     $SQLUpdate = "UPDATE data SET status = 2, error='$error' WHERE number = " . $data['number'];
     $update = $db->prepare($SQLUpdate);
     $update->execute();
     return false
   }


     //good?
     $SQLUpdate = "UPDATE data SET status = 1 WHERE number = " . $data['number'];
     $update = $db->prepare($SQLUpdate);
     $update->execute();


    $SQLInsert = "INSERT INTO tbl_done .....";
    $SQLInsert = $db->prepare($SQLInsert);
    $SQLInsert->execute();
}

top команда:

top - 10:48:54 up 17 days, 10:30,  2 users,  load average: 1.06, 1.05, 1.01
Tasks: 188 total,   1 running, 187 sleeping,   0 stopped,   0 zombie
Cpu(s): 25.8%us,  0.1%sy,  0.0%ni, 74.1%id,  0.0%wa,  0.0%hi,  0.1%si,  0.0%st
Mem:   4138464k total,  1908724k used,  2229740k free,   316224k buffers
Swap:  2096440k total,       16k used,  2096424k free,   592384k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
32183 mysql     15   0  903m 459m 4800 S 101.8 11.4 876:53.66 mysqld

-

/etc/my.cnf
[mysqld]
set-variable = max_connections=500
safe-show-database
max_user_connections=200
key_buffer_size = 16M
query_cache_size = 350M
tmp_table_size = 200M
max_heap_table_size  = 200M
thread_cache_size = 4
table_cache = 800
thread_concurrency = 8
innodb_buffer_pool_size = 400M
innodb_log_file_size = 128M
query_cache_limit = 500M
innodb_flush_log_at_trx_commit = 2

Спецификация сервера: Intel Core 2 Quad Q8300, 2,5 ГГц, 4 ГБ ОЗУ.

'mysqladmin proc':

+------+-----------------+-----------+----------------+---------+------+----------+-------------------------------------------------------------------------------
| Id   | User            | Host      | db             | Command | Time | State    | Info                                                                          
+------+-----------------+-----------+----------------+---------+------+----------+--------------------------------------------------------------------------------
|  265 | user            | localhost | xxxxxxxxxxxxxx | Query   |   15 | Updating | UPDATE data SET status = '2', error = 'Unknown error'  WHERE number= 0xxxxx    
|  269 | user            | localhost | xxxxxxxxxxxxxx | Query   |   17 | Updating | UPDATE data SET status = '2', error = 'Invalid ....'  WHERE number= 0xxx 
|  280 | user            | localhost | xxxxxxxxxxxxxx | Query   |    7 | Updating | UPDATE data SET status = 1  WHERE f = 0xxxx                                           
|  300 | user            | localhost | xxxxxxxxxxxxxx | Query   |    1 | Updating | UPDATE data SET status = '2', error = 'Unknown ....'  WHERE number= 0xx             
|  314 | user            | localhost | xxxxxxxxxxxxxx | Query   |   13 | Updating | UPDATE data SET status = '2', error = 'Invalid....'  WHERE number= 0xxxx
|  327 | user            | localhost | xxxxxxxxxxxxxx | Query   |   11 | Updating | UPDATE data SET status = '2', error = 'Unknown ....'  WHERE number= 0xxxx               
|  341 | user            | localhost | xxxxxxxxxxxxxx | Sleep   |    2 |          | NULL                                                                                      
|  350 | user            | localhost | xxxxxxxxxxxxxx | Query   |    7 | Updating | UPDATE data SET status = '2', error = 'Unknown ....'  WHERE number= 0xxx                
|  360 | user            | localhost | xxxxxxxxxxxxxx | Query   |    5 | Updating | UPDATE data SET status = 1  WHERE number = 0xxxx     

Объясните:

+----+-------------+-------+-------------+----------------+----------------+---------+------+-------+----------------------------------------------+
| id | select_type | table | type        | possible_keys  | key            | key_len | ref  | rows  | Extra                                        |
+----+-------------+-------+-------------+----------------+----------------+---------+------+-------+----------------------------------------------+
|  1 | SIMPLE      | data  | index_merge | process,status | process,status | 52,1    | NULL | 16439 | Using intersect(process,status); Using where |
+----+-------------+-------+-------------+----------------+----------------+---------+------+-------+----------------------------------------------+

Ответы [ 2 ]

2 голосов
/ 20 декабря 2011

Когда вы выполняете запрос select, вы получаете блокировки чтения для читаемых строк.А в методе проверки вы пытаетесь обновить читаемую (= заблокированную) строку.Таким образом, MySQL ставит в очередь запрос на обновление, который будет выполнен, как только запрос на выборку будет снят запросом select.Но так как вы приостановили выполнение на две секунды для каждой строки, вы увеличиваете задержку для снятия блокировки, что, в свою очередь, задерживает каждый запрос, ожидающий в очереди обновлений.Вы можете прочитать больше о режимах блокировки innodb .

Я бы предложил изменить код следующим образом:

  • ограничить ваш запрос на выборку, чтобы он возвращал только ограниченное количествостроки и убедитесь, что он выберет оставшиеся строки во время следующей итерации.Вы можете достичь этого, используя операторы offset и limit в запросе select.
  • Считайте все строки запроса select в массив переменных и освободите запрос, чтобы освободить блокировки чтения.а также
  • Перебирайте массив чисел, обновляйте каждую строку
  • Продолжайте с первого шага, с которого вы остановились.

ОБНОВЛЕНИЕ

Вы используете fetch для извлечения строк из набора результатов.Согласно документации :

Извлекает строку из результирующего набора, связанного с объектом PDOStatement

, чтобы получить все строки одновременно, вы должны использовать fetchAll, но будьте осторожны с проблемами производительности, поскольку в документации указано:

Использование этого метода для извлечения больших наборов результатов приведет к высокой нагрузке на систему ивозможно, сетевые ресурсы.

Вот почему я предложил ограничить запрос для получения определенного количества строк вместо всего набора результатов (состоящего из 100 000+ строк).Вы можете ограничить количество возвращаемых строк, изменив ваш запрос следующим образом:

SELECT * FROM data WHERE status = 0 AND process = '1' LIMIT 10000 OFFSET 0

А затем, когда вы выполняете запрос второй раз, запустите запрос как:

SELECT * FROM data WHERE status = 0 AND process = '1' LIMIT 10000 OFFSET 10000

Вы можетепродолжайте в том же духе, пока не получите никаких результатов.

0 голосов
/ 20 декабря 2011
  1. Вы обновляете строки с ошибками до статуса = 2, затем все строки до статуса = 1 - при условии, что это опечатка (пропущено еще)

  2. ЕслиВы действительно спите 2 секунды между каждой строкой, было бы разумнее выбирать с «пределом 1» и повторять запрос на выборку каждые 2 секунды - это может объяснить ваши блокировки, если число не уникально

...