提供大量数据的查询的最佳MySQL设置?
我是一名科学家,我使用MySQL作为数值模拟结果的存储。 通常情况下,我有一套通过我的实验和一套控制系统获得的数据。 这两个数据集存储在一个表中。 一个指标字段告诉我logging是来自实验还是来自控制集。 这个表通常有约1亿条logging。 5000万次实验和5000万次控制。
当我对数据进行后处理时,我的典型任务包括首先发出以下两个查询:
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的多列索引。 这些查询花费了大量时间,查询大部分时间都在“发送数据”
我正在使用12GB内存的8core MacPro上运行。 我是这台机器的一个用户,这个任务是主要的任务,因此我可以将所有的RAM专用于MySQL。 所有表是MyISAM(我可以将它们转换,如果这会提高我的查询速度)。
我将不胜感激任何关于如何加快这些查询的build议。 我应该改变一些设置,指数,查询….
在这些查询中,我预计会得到约5000万条logging。 请注意,由于pipe理原因,将表格拆分成两个表格,其中一个包含实验,另一个包含对照观察。
这里是输出:
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 |
如果您的查询需要花费2个小时才能执行,那么在类似的硬件上,我可以在60秒内完成相同的操作。
以下的一些可能会有帮助…
调整您的引擎的MySQL
检查您的服务器configuration并相应地优化。 下面的一些资源应该是有用的。
- http://www.mysqlperformanceblog.com/2006/09/29/what-to-tune-in-mysql-server-after-installation/
- http://www.mysqlperformanceblog.com/
- http://www.highperfmysql.com/
- http://forge.mysql.com/wiki/ServerVariables
- http://dev.mysql.com/doc/refman/5.0/en/server-system-variables.html
- http://www.xaprb.com/blog/2006/07/04/how-to-exploit-mysql-index-optimizations/
- http://jpipes.com/presentations/perf_tuning_best_practices.pdf
- http://jpipes.com/presentations/index_coding_optimization.pdf
- http://www.jasny.net/?p=36
现在不太明显…
考虑使用存储过程来处理数据服务器端
为什么不处理MySQL内的所有数据,所以你不必发送大量的数据到你的应用程序层呢? 以下示例使用游标在2分钟内循环处理50M行服务器端。 我不是游标的狂热粉丝,特别是在MySQL中,它们非常有限,但是我猜你会循环结果集并进行某种forms的数值分析,所以在这种情况下使用游标是合理的。
简化的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
存储过程
我创build了一个简单的存储过程来获取所需的数据并对其进行处理(使用与您的示例相同的条件)
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)
嗯,performance有点令人失望,所以下一个想法。
考虑使用innodb引擎(shock horror)
为什么innodb? 因为它有聚簇索引! 你会发现使用innodb插入速度较慢,但希望阅读速度会更快,所以这是一个折衷的方法,可能是值得的。
通过聚集索引访问行很快,因为行数据位于索引search所在的同一页面上。 如果表很大,那么与使用与索引logging不同页面存储行数据的存储组织相比,聚簇索引体系结构通常会节省磁盘I / O操作。 例如,MyISAM使用一个文件作为数据行,另一个作为索引logging。
更多信息:
简化的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表中select数据。
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)
比myisam发动机的实施速度快大约2-3分钟 ! (innodb FTW)
分而治之
在使用游标的服务器端存储过程中处理结果可能不是一个最佳的解决scheme,尤其是因为MySQL不支持诸如C#等3GL语言或甚至其他数据库中的数据和复杂的数据结构作为Oracle PL / SQL。
所以这里的想法是将批量数据返回到应用层(C#任何),然后可以将结果添加到基于集合的数据结构,然后在内部处理数据。
存储过程
该存储过程需要3个参数rc,df_low和df_high,它允许您select一系列的数据,如下所示:
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版本。
我开发了一个快速而肮脏的multithreadingC#应用程序,它将调用存储过程并将结果添加到集合中以进行后期查询处理。 你不必使用线程,相同的批量查询方法可以顺序完成,没有太多的性能损失。
每个线程(QueryThread)select一系列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
因此,在60秒内获取5000万行并添加到集合中。
我使用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;
在插入并在事务中包装整个事物之前,由innodb PKsorting的结果将会加快速度。
我希望这一些certificate是有帮助的。
祝你好运