草庐IT

mysql - 用于提供大量数据的查询的最佳 MySQL 设置?

coder 2023-10-02 原文

我是一名科学家,我使用 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 |

最佳答案

当我可以在 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)
    

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

    分而治之

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

    所以这里的想法是将成批的数据返回到应用程序层(无论是 C# 还是 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 设置?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4129071/

    有关mysql - 用于提供大量数据的查询的最佳 MySQL 设置?的更多相关文章

    1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

      我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

    2. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

      很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

    3. ruby-openid:执行发现时未设置@socket - 2

      我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

    4. ruby - ECONNRESET (Whois::ConnectionError) - 尝试在 Ruby 中查询 Whois 时出错 - 2

      我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.

    5. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

      大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

    6. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

      我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

    7. ruby-on-rails - 如何使用 instance_variable_set 正确设置实例变量? - 2

      我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击

    8. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

      我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

    9. ruby-on-rails - 在 Rails 和 ActiveRecord 中查询时忽略某些字段 - 2

      我知道我可以指定某些字段来使用pluck查询数据库。ids=Item.where('due_at但是我想知道,是否有一种方法可以指定我想避免从数据库查询的某些字段。某种反拔?posts=Post.where(published:true).do_not_lookup(:enormous_field) 最佳答案 Model#attribute_names应该返回列/属性数组。您可以排除其中一些并传递给pluck或select方法。像这样:posts=Post.where(published:true).select(Post.attr

    10. Ruby Sinatra 配置用于生产和开发 - 2

      我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

    随机推荐