我是一名科学家,我使用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内部的所有数据,这样您就不必向应用层发送大量数据?以下示例使用游标在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插入速度较慢,但希望它读起来会更快,所以这是一个可能值得的折衷.
通过聚簇索引访问行很快,因为行数据位于索引搜索所在的同一页面上.如果表很大,与使用与索引记录不同的页面存储行数据的存储组织相比,聚簇索引体系结构通常会保存磁盘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)
在我的简化系统中,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对结果进行排序将加快速度.
我希望其中一些证明是有帮助的.
祝好运
| 归档时间: |
|
| 查看次数: |
7341 次 |
| 最近记录: |