Оптимальные настройки MySQL для запросов, которые доставляют большие объемы данных? - PullRequest
7 голосов
/ 09 ноября 2010

Я работаю учёным и использую MySQL как хранилище для результатов моих численных расчетов.Обычно у меня есть набор данных, полученных в результате эксперимента, и контрольный набор.Эти два набора данных хранятся в одной таблице.Одно поле индикатора говорит мне, поступила ли запись из эксперимента или из контрольного набора.Эта таблица обычно содержит около 100 миллионов записей.50 миллионов экспериментов и 50 миллионов контролей.

Когда я выполняю постобработку своих данных, моя типичная задача состоит в том, чтобы сначала выполнить следующие два запроса:

select b0,t0 from results_1mregr_c_ew_f where RC='E' and df>60  /// getting experiments data 

и

select b0,t0 from results_1mregr_c_ew_f where RC='C' and df>60 /// getting controls data

У меня есть несколькоиндекс колонки на RC, df.Эти запросы занимают много времени, а запросы проводят большую часть времени «Отправка данных»

Я выполняю это на 8-ядерном MacPro с 12 ГБ ОЗУ.Я один пользователь этой машины, и эта задача является основной задачей, поэтому я могу выделить всю оперативную память для MySQL.Все таблицы являются MyISAM (я могу преобразовать их, если это увеличит скорость моих запросов).

Буду признателен за любые рекомендации по ускорению этих запросов.Должен ли я изменить некоторые параметры, индексы, запросы ...

В каждом из этих запросов я ожидаю получить ~ 50 миллионов записей. Обратите внимание, что разделение таблицы на две таблицы, содержащую однуэкспериментальное и одно контрольное наблюдение не представляется возможным по административным причинам.

Вот вывод:

explain select b0, t0 from results_1mregr_c_ew_f  where RC="C" and df>60
+----+-----------+---------------------+-----+-------------+---+-------+----+-------+-----------+
| id |select_type|table                |type |possible_keys|key|key_len|ref |rows   |Extra      |
+----+-----------+---------------------+-----+-------------+---+-------+----+-------+-----------+
|  1 |SIMPLE     |results_1mregr_c_ew_f|range|ff           |ff |11     |NULL|6251121|Using where|
+----+-----------+---------------------+-----+-------------+---+-------+----+-------+-----------+

Вот вывод:

show indexes from results_1mregr_c_ew_f;
+-----------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table                 | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-----------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| results_1mregr_c_ew_f |          0 | PRIMARY  |            1 | id          | A         |    50793996 |     NULL | NULL   |      | BTREE      |         |
| results_1mregr_c_ew_f |          1 | ff       |            1 | RC          | A         |           3 |     NULL | NULL   |      | BTREE      |         |
| results_1mregr_c_ew_f |          1 | ff       |            2 | df          | A         |         120 |     NULL | NULL   |      | BTREE      |         |
+-----------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

Вот вывод:

CREATE TABLE `results_1mregr_c_ew_f` (
  `b0` double NOT NULL COMMENT '    ',
  `s0` double NOT NULL,
  `t0` double NOT NULL,
  `b1` double NOT NULL,
  `s1` double NOT NULL,
  `t1` double NOT NULL,
  `b2` double NOT NULL,
  `s2` double NOT NULL,
  `t2` double NOT NULL,
  `b3` double NOT NULL,
  `s3` double NOT NULL,
  `t3` double NOT NULL,
  `b4` double NOT NULL,
  `s4` double NOT NULL,
  `t4` double NOT NULL,
  `AD` char(4) NOT NULL,
  `chisq` double NOT NULL,
  `RC` char(7) NOT NULL,
  `colq` varchar(255) NOT NULL,
  `df` int(11) NOT NULL,
  `ncol` int(11) NOT NULL,
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `p1` float NOT NULL,
  `p2` float NOT NULL,
  `p3` float NOT NULL,
  `p4` float NOT NULL,
  PRIMARY KEY (`id`),
  KEY `ff` (`RC`,`df`)
) ENGINE=MyISAM AUTO_INCREMENT=50793997 DEFAULT CHARSET=ascii |

1 Ответ

36 голосов
/ 10 ноября 2010

Что-то должно быть серьезно неправильно, чтобы ваш запрос выполнялся за 2 часа, когда я могу сделать то же самое менее чем за 60 секунд на аналогичном оборудовании.

Некоторые из следующих функций могут оказаться полезными ...

Настройка MySQL для вашего движка

Проверьте конфигурацию вашего сервера и оптимизируйте соответственно.Некоторые из следующих ресурсов должны быть полезны:

Теперь для менее очевидного ...

Подумайте об использовании хранимой процедуры для обработки на стороне сервера данных

Почему бы не обработать все данные внутри MySQL, чтобы вам не приходилось отправлятьогромное количество данных для вашего прикладного уровня?В следующем примере курсор используется для зацикливания и обработки 50M строк на стороне сервера менее чем за 2 минуты.Я не большой поклонник курсоров, особенно в MySQL, где они очень ограничены, но я предполагаю, что вы будете зацикливать набор результатов и выполнять какую-то форму численного анализа, поэтому использование курсора в этом случае оправдано.

Упрощенная таблица результатов myisam - ключи основаны на ваших.

drop table if exists results_1mregr_c_ew_f;
create table results_1mregr_c_ew_f
(
id int unsigned not null auto_increment primary key,
rc tinyint unsigned not null,
df int unsigned not null default 0,
val double(10,4) not null default 0,
ts timestamp not null default now(),
key (rc, df)
)
engine=myisam;

Я сгенерировал 100M строк данных с полями ключей, имеющих примерно ту же мощность, что и в вашем примере:

show indexes from results_1mregr_c_ew_f;

Table                   Non_unique  Key_name    Seq_in_index    Column_name Collation   Cardinality Index_type
=====                   ==========  ========    ============    =========== =========   =========== ==========
results_1mregr_c_ew_f       0       PRIMARY         1               id          A       100000000   BTREE   
results_1mregr_c_ew_f       1       rc              1               rc          A               2   BTREE   
results_1mregr_c_ew_f       1       rc              2               df          A             223   BTREE   

Хранимая процедура

Я создал простую хранимую процедуру, которая извлекает требуемые данные и обрабатывает их (использует те же условия, что и в вашем примере)

drop procedure if exists process_results_1mregr_c_ew_f;

delimiter #

create procedure process_results_1mregr_c_ew_f
(
in p_rc tinyint unsigned,
in p_df int unsigned
)
begin

declare v_count int unsigned default 0;
declare v_done tinyint default 0;
declare v_id int unsigned;
declare v_result_cur cursor for select id from results_1mregr_c_ew_f where rc = p_rc and df > p_df;
declare continue handler for not found set v_done = 1;

open v_result_cur;

repeat
    fetch v_result_cur into v_id;

    set v_count = v_count + 1;
    -- do work...

until v_done end repeat;
close v_result_cur;

select v_count as counter;

end #

delimiter ; 

Были замечены следующие времена выполнения:

call process_results_1mregr_c_ew_f(0,60);

runtime 1 = 03:24.999 Query OK (3 mins 25 secs)
runtime 2 = 03:32.196 Query OK (3 mins 32 secs)

call process_results_1mregr_c_ew_f(1,60);

runtime 1 = 04:59.861 Query OK (4 mins 59 secs)
runtime 2 = 04:41.814 Query OK (4 mins 41 secs)

counter
========
23000002 (23 million rows processed in each case)

Хмммм, производительность немного разочаровывает, поэтому перейдем к следующей идее.

Рассмотрите возможность использования движка innodb (шок ужас)

Почему Иннодб ??потому что он имеет кластерные индексы!Вы обнаружите, что вставка выполняется медленнее с использованием innodb, но, надеюсь, будет быстрее читать, так что это компромисс, который может стоить.

Быстрый доступ к строке через кластерный индекс, потому что данные строки находятся на той же страницекуда ведет поиск по индексу.Если таблица большая, архитектура кластерного индекса часто сохраняет операции ввода-вывода на диске по сравнению с организациями хранения, которые хранят данные строк, используя страницу, отличную от записи индекса.Например, MyISAM использует один файл для строк данных, а другой - для индексных записей.

Дополнительная информация здесь:

Упрощенная таблица результатов innodb

drop table if exists results_innodb;
create table results_innodb
(
rc tinyint unsigned not null,
df int unsigned not null default 0,
id int unsigned not null, -- cant auto_inc this !!
val double(10,4) not null default 0,
ts timestamp not null default now(),
primary key (rc, df, id) -- note clustered (innodb only !) composite PK
)
engine=innodb;

Одна проблема с innodb заключается в том, что она не поддерживает поля auto_increment, которые образуют часть составного ключа, поэтому вам придется самим увеличивать значение ключа с помощью генератора последовательности, триггер или другой метод - возможно, в приложении, заполняющем саму таблицу результатов ??

Опять же, я сгенерировал 100M строк данных с ключевыми полями, имеющими примерно ту же мощность, что и в вашем примере.Не беспокойтесь, если эти цифры не соответствуют примеру myisam, так как innodb оценивает количество элементов, поэтому они не будут одинаковыми.(но они - тот же самый набор данных)

show indexes from results_innodb;

Table           Non_unique  Key_name    Seq_in_index    Column_name Collation   Cardinality Index_type
=====           ==========  ========    ============    =========== =========   =========== ==========
results_innodb      0       PRIMARY         1               rc          A                18     BTREE   
results_innodb      0       PRIMARY         2               df          A                18     BTREE   
results_innodb      0       PRIMARY         3               id          A         100000294     BTREE   

Хранимая процедура

Хранимая процедура точно такая же, как в примере myisam выше, но выбирает данные из innodbвместо таблицы.

declare v_result_cur cursor for select id from results_innodb where rc = p_rc and df > p_df;

Результаты следующие:

call process_results_innodb(0,60);

runtime 1 = 01:53.407 Query OK (1 mins 53 secs)
runtime 2 = 01:52.088 Query OK (1 mins 52 secs)

call process_results_innodb(1,60);

runtime 1 = 02:01.201 Query OK (2 mins 01 secs)
runtime 2 = 01:49.737 Query OK (1 mins 50 secs)

counter
========
23000002 (23 million rows processed in each case)

приблизительно на 2-3 минуты быстрее , чем реализация движка myisam!(innodb FTW)

Разделяй и властвуй

Обработка результатов в хранимой процедуре на стороне сервера, в которой используется курсор, может быть неоптимальным решением, тем более что MySQL не поддерживает такие вещи, как массивы и сложныеструктуры данных, которые легко доступны на языках 3GL, таких как C # и т. д., или даже в других базах данных, таких как Oracle PL / SQL.

Таким образом, идея состоит в том, чтобы возвращать пакеты данных на прикладной уровень (C # независимо от того), которыйзатем можно добавить результаты в структуру данных на основе коллекции, а затем обработать данные для внутреннего использования.

Хранимая процедура

Хранимая процедура принимает 3 параметра rc, df_low и df_high, что позволяет вам выбрать диапазон данных следующим образом:

call list_results_innodb(0,1,1); -- df 1
call list_results_innodb(0,1,10); -- df between 1 and 10
call list_results_innodb(0,60,120); -- df between 60 and 120 etc...

очевидночем выше диапазон df, тем больше данных вы будете извлекать.

drop procedure if exists list_results_innodb;

delimiter #

create procedure list_results_innodb
(
in p_rc tinyint unsigned,
in p_df_low int unsigned,
in p_df_high int unsigned
)
begin
    select rc, df, id from results_innodb where rc = p_rc and df between p_df_low and p_df_high;
end #

delimiter ; 

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

call list_results_1mregr_c_ew_f(0,1,1);
call list_results_1mregr_c_ew_f(0,1,10);
call list_results_1mregr_c_ew_f(0,60,120);

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

Я разработал быстрое и грязное многопоточное приложение C #, которое будет вызывать хранимую процедуруи добавить результаты в коллекцию для последующей обработки запроса.Вам не нужно использовать потоки, один и тот же подход к пакетным запросам может быть выполнен последовательно без значительной потери производительности.

Каждый поток (QueryThread) выбирает диапазон данных df, зацикливает набор результатов и добавляет каждый результат (строку)в коллекцию результатов.

class Program
    {
        static void Main(string[] args)
        {
            const int MAX_THREADS = 12; 
            const int MAX_RC = 120;

            List<AutoResetEvent> signals = new List<AutoResetEvent>();
            ResultDictionary results = new ResultDictionary(); // thread safe collection

            DateTime startTime = DateTime.Now;
            int step = (int)Math.Ceiling((double)MAX_RC / MAX_THREADS) -1; 

            int start = 1, end = 0;
            for (int i = 0; i < MAX_THREADS; i++){
                end = (i == MAX_THREADS - 1) ? MAX_RC : end + step;
                signals.Add(new AutoResetEvent(false));

                QueryThread st = new QueryThread(i,signals[i],results,0,start,end);
                start = end + 1;
            }
            WaitHandle.WaitAll(signals.ToArray());
            TimeSpan runTime = DateTime.Now - startTime;

            Console.WriteLine("{0} results fetched and looped in {1} secs\nPress any key", results.Count, runTime.ToString());
            Console.ReadKey();
        }
    }

Время выполнения наблюдается следующим образом:

Thread 04 done - 31580517
Thread 06 done - 44313475
Thread 07 done - 45776055
Thread 03 done - 46292196
Thread 00 done - 47008566
Thread 10 done - 47910554
Thread 02 done - 48194632
Thread 09 done - 48201782
Thread 05 done - 48253744
Thread 08 done - 48332639
Thread 01 done - 48496235
Thread 11 done - 50000000
50000000 results fetched and looped in 00:00:55.5731786 secs
Press any key

Таким образом, 50 миллионов строк извлекаются и добавляются в коллекцию менее чем за 60 секунд.

Iпопытался сделать то же самое, используя хранимую процедуру myisam, которая заняла 2 минуты.

50000000 results fetched and looped in 00:01:59.2144880 secs

Переход на innodb

В моей упрощенной системе таблица myisam работает не слишком плохо, так что это может бытьстоит перейти на innodb.Если вы решили скопировать свои данные результатов в таблицу innodb, сделайте это следующим образом:

start transaction;

insert into results_innodb 
 select <fields...> from results_1mregr_c_ew_f order by <innodb primary key>;

commit;

Упорядочивание результата с помощью PK innodb перед вставкой и упаковкой всего объекта в транзакцию ускорит процесс..

Я надеюсь, что кое-что из этого окажется полезным.

Удачи

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...