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

Posted

技术标签:

【中文标题】提供大量数据的查询的最佳 MySQL 设置?【英文标题】:Optimal MySQL settings for queries that deliver large amounts of data? 【发布时间】:2011-05-06 22:57:50 【问题描述】:

我是一名科学家,我使用 mysql 作为数值模拟结果的存储设备。通常我有一组通过我的实验获得的数据和一个控制组。这两个数据集存储在一个表中。一个指标字段告诉我记录是来自实验还是来自控制集。该表通常有约 1 亿条记录。 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 RAM 的 8core MacPro 上运行它。 我是这台机器的单个用户,这个任务是主要任务,因此我可以将所有 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|
+----+-----------+---------------------+-----+-------------+---+-------+----+-------+-----------+

下面是输出:

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 |

【问题讨论】:

您应该发布SHOW CREATE TABLE resultsEXPLAIN SELECT ... 的输出,以便我们更好地了解发生了什么。 MySQL 可能没有使用您的索引,因为它认为表扫描会更快。你要返回多少行? 添加到上面...显示结果中的索引;谢谢 您能在此处发布您的表架构吗? 完成,很抱歉没有在我的第一篇文章中发布此信息。 目前处理 1 批 5000 万行需要多长时间? 【参考方案1】:

如果我可以在类似的硬件上在 60 秒内完成相同的事情,那么您的查询需要 2 小时才能执行,这一定是严重错误。

以下一些内容可能会有所帮助...

为您的引擎调整 MySQL

检查您的服务器配置并进行相应优化。以下一些资源应该很有用。

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 中游标非常有限,但我猜你会循环结果集并进行某种形式的数值分析,因此在这种情况下使用游标是合理的。

简化的 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   

存储过程

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

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 ??因为它有聚集索引!您会发现使用 innodb 插入速度较慢,但​​希望读取速度更快,因此这是值得的权衡。

通过聚集索引访问行速度很快,因为行数据位于索引搜索引导的同一页面上。如果表很大,与使用与索引记录不同的页面存储行数据的存储组织相比,聚集索引架构通常会节省磁盘 I/O 操作。例如,MyISAM 使用一个文件 数据行和另一个用于索引记录。

更多信息在这里:

http://dev.mysql.com/doc/refman/5.0/en/innodb-index-types.html

简化的 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)

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

分而治之

在使用游标的服务器端存储过程中处理结果可能不是最佳解决方案,尤其是因为 MySQL 不支持诸如 C# 等 3GL 语言或什至在其他数据库,例如 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

因此,在 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 PK 对结果进行排序将加快处理速度。

我希望其中的一些内容对您有所帮助。

祝你好运

【讨论】:

以上是关于提供大量数据的查询的最佳 MySQL 设置?的主要内容,如果未能解决你的问题,请参考以下文章

构建具有大量数据通信的系统的最佳方法是啥?

MySQL 快速删除大量数据

从数据存储区查询大量 ndb 实体的最佳实践

mysql对于大量数据,怎么进行优化

MySQL--查询性能优化

优化返回大量记录的查询,避免数百个连接。这是一个聪明的解决方案吗?