为提供大量数据的查询提供最佳MySQL设置?

Ras*_*sha 7 mysql performance

我是一名科学家,我使用MySQL作为数值模拟结果的存储空间.通常我有一组通过我的实验和控制集获得的数据.这两个数据集存储在一个表中.一个指示器字段告诉我记录是来自实验还是来自控制集.该表通常有大约1亿条记录.5000万次实验和5000万次对照.

当我对数据进行后期处理时,我的典型任务包括首先发出以下两个查询:

select b0,t0 from results_1mregr_c_ew_f where RC='E' and df>60  /// getting experiments data 
Run Code Online (Sandbox Code Playgroud)

select b0,t0 from results_1mregr_c_ew_f where RC='C' and df>60 /// getting controls data
Run Code Online (Sandbox Code Playgroud)

我在RC上有一个多列索引,df.这些查询需要花费大量时间,查询大部分时间都会花费"发送数据"

我在8核MacPro上使用12GB RAM运行它.我是这台机器的单个用户,这个任务是主要任务,因此我可以将所有RAM专用于MySQL.所有表都是MyISAM(我可以转换它们,如果这会增加我的查询速度).

我很感激有关如何加快这些查询的任何建议.我应该更改一些设置,索引,查询....

在每个查询中,我希望能够获得大约5000万条记录. 请注意,由于管理原因,将表拆分为两个表,其中一个包含实验,另一个包含控制观察.

这是输出:

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|
+----+-----------+---------------------+-----+-------------+---+-------+----+-------+-----------+
Run Code Online (Sandbox Code Playgroud)

以下是来自的输出:

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      |         |
+-----------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
Run Code Online (Sandbox Code Playgroud)

以下是来自的输出:

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 |
Run Code Online (Sandbox Code Playgroud)

Jon*_*ack 36

当我在类似的硬件上在60秒内完成同样的事情时,你的查询需要花费2个小时才能完全出错.

以下一些可能有用......

为您的引擎调整MySQL

检查服务器配置并进行相应优化.以下某些资源应该是有用的.

现在不太明显......

考虑使用存储过程来处理数据服务器端

为什么不处理MySQL内部的所有数据,这样您就不必向应用层发送大量数据?以下示例使用游标在2分钟内循环并处理50M行服务器端.我不是游标的忠实粉丝,特别是在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;
Run Code Online (Sandbox Code Playgroud)

我生成了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   
Run Code Online (Sandbox Code Playgroud)

存储过程

我创建了一个简单的存储过程来获取所需的数据并对其进行处理(使用与示例相同的条件)

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 ; 
Run Code Online (Sandbox Code Playgroud)

观察到以下运行时间:

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)
Run Code Online (Sandbox Code Playgroud)

嗯,性能有点令人失望,所以下一个想法.

考虑使用innodb引擎(震惊恐怖)

为什么innodb ?? 因为它有聚集索引!你会发现使用innodb插入速度较慢,但​​希望它读起来会更快,所以这是一个可能值得的折衷.

通过聚簇索引访问行很快,因为行数据位于索引搜索所在的同一页面上.如果表很大,与使用与索引记录不同的页面存储行数据的存储组织相比,聚簇索引体系结构通常会保存磁盘I/O操作.例如,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;
Run Code Online (Sandbox Code Playgroud)

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   
Run Code Online (Sandbox Code Playgroud)

存储过程

存储过程与上面的myisam示例完全相同,但是从innodb表中选择数据.

declare v_result_cur cursor for select id from results_innodb where rc = p_rc and df > p_df;
Run Code Online (Sandbox Code Playgroud)

结果如下:

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)
Run Code Online (Sandbox Code Playgroud)

比myisam引擎实现2-3分钟!(innodb FTW)

分而治之

在使用游标的服务器端存储过程中处理结果可能不是最佳解决方案,尤其是因为MySQL不支持诸如C#等3GL语言中甚至在其他数据库中可用的数组和复杂数据结构.作为Oracle PL/SQL.

因此,这里的想法是将批量数据返回到应用程序层(C#whatever),然后可以将结果添加到基于集合的数据结构,然后在内部处理数据.

存储过程

存储过程采用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...
Run Code Online (Sandbox Code Playgroud)

显然,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 ; 
Run Code Online (Sandbox Code Playgroud)

我还敲了一个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);
Run Code Online (Sandbox Code Playgroud)

基于上面的光标示例,我希望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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

运行时观察如下:

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
Run Code Online (Sandbox Code Playgroud)

因此,在60秒内获取了5000万行并添加到集合中.

我使用myisam存储过程尝试了同样的事情,花了2分钟才完成.

50000000 results fetched and looped in 00:01:59.2144880 secs
Run Code Online (Sandbox Code Playgroud)

搬到innodb

在我的简化系统中,myisam表执行得不是很糟糕,因此可能不值得迁移到innodb.如果您决定将结果数据复制到innodb表,请按以下步骤操作:

start transaction;

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

commit;
Run Code Online (Sandbox Code Playgroud)

在插入并在事务中包装整个事物之前,通过innodb PK对结果进行排序将加快速度.

我希望其中一些证明是有帮助的.

祝好运

  • 给出答案的+1,而不仅仅是解决:) (5认同)