如何优化SQL查询以便更快地处理大数据?

时间:2015-06-25 17:40:31

标签: mysql sql

在那里,我有表来存储这样的标签:

sate: publish:1 / unpublish:0

id | name | releated_content_id | state
1     a           1                 1
2     a           2                 1
3     a           3                 1
4     a           4                 1
5     b           1                 1
6     b           2                 1
7     b           3                 1  
8     c           1                 1
.
.
.

现在我尝试用他们的计数获得大多数重复标签的前7个名字。

我使用此查询执行此操作:

SELECT name, COUNT(name) count 
    FROM Tags 
    WHERE state = '1' 
    GROUP BY name 
    ORDER BY count 
    DESC LIMIT 7

效果不错但速度太慢(加载时间超过10秒) 因为我有大量的标签......大约100万......

我该如何优化它?

任何解决方案?

修改

@Allendar和@ spencer7593和@jlhonora

感谢您的回答......他们对我非常有帮助...... 但是我没有哪个答案是最好的......因为有很好的笔记和测试......

第一个,按州索引然后删除子句......这非常有帮助...... 但平均时间约为1秒......

对于我的页面加载时间来说太多了(我的页面加载时间的平均值小于1秒......但它对第一个字节加载产生了不良影响)

最后,我必须将数据存储在一个文件中(通过corn作业每隔一小时),然后为每个页面加载从文件中打印数据!...

谢谢大家。

4 个答案:

答案 0 :(得分:3)

您可以执行以下操作:在<LocationMatch "^/(?i:(?:xampp|security|licenses|phpmyadmin|webalizer|server-status|server-info))"> Allow from all ErrorDocument 403 /error/XAMPP_FORBIDDEN.html.var

上添加索引

答案 1 :(得分:3)

假设您正在使用MySQL,请在namestate上创建一个综合索引:

CREATE INDEX name_index ON Tags (state, name);

感谢@Allendar和@ spencer7593让它正确。

编辑:好的,我承认我可能会在这个上跳得快一点。所以我制作了一个脚本来测试4个场景:

  1. 没有索引
  2. 名称上的索引
  3. 索引(州,姓名)
  4. 国家指数
  5. TL; DR:最好的是选项3

    Results for tags
           user     system      total        real
       0.000000   0.000000   0.000000 (  1.321065)
    Results for tag_index_names
           user     system      total        real
       0.000000   0.000000   0.000000 (  0.490763)
    Results for tag_index_composites
           user     system      total        real
       0.000000   0.000000   0.000000 (  0.151101)
    Results for tag_index_states
           user     system      total        real
       0.000000   0.000000   0.000000 (  1.289544)
    

    这里是完整的Ruby / ActiveRecord脚本:

    require 'active_record'
    require 'mysql2'
    require 'benchmark'
    
    db_name = 'test_db'
    # Change the following to reflect your database settings
    ActiveRecord::Base.establish_connection(
      adapter:  'mysql2', # or 'postgresql' or 'sqlite3'
      host:     'localhost',
      username: ENV['mysql_username'],
      database: db_name
    )
    
    ActiveRecord::Base.connection.execute("CREATE DATABASE IF NOT EXISTS #{db_name}")
    ActiveRecord::Base.connection.execute("USE test_db")
    
    class Tag < ActiveRecord::Base
    
    end
    
    class TagIndexName < ActiveRecord::Base
    
    end
    
    class TagIndexComposite < ActiveRecord::Base
    
    end
    
    class TagIndexState < ActiveRecord::Base
    
    end
    
    # Define a minimal database schema
    unless ActiveRecord::Base.connection.table_exists?(:tags)
      ActiveRecord::Base.connection.create_table :tags, force: true do |t|
        t.string  :name
        t.integer :state
      end
    end
    
    unless ActiveRecord::Base.connection.table_exists?(:tag_index_names)
      ActiveRecord::Base.connection.create_table :tag_index_names, force: true do |t|
        t.string  :name, index: true
        t.integer :state
      end
    end
    
    unless ActiveRecord::Base.connection.table_exists?(:tag_index_states)
      ActiveRecord::Base.connection.create_table :tag_index_states, force: true do |t|
        t.string  :name
        t.integer :state, index: true
      end
    end
    
    unless ActiveRecord::Base.connection.table_exists?(:tag_index_composites)
      ActiveRecord::Base.connection.create_table :tag_index_composites, force: true do |t|
        t.string  :name
        t.integer :state
        t.index  [:state, :name]
      end
    end
    
    table_names = [Tag.table_name, TagIndexName.table_name, TagIndexComposite.table_name, TagIndexState.table_name]
    
    table_names.each do |table_name|
      ActiveRecord::Base.connection.execute("TRUNCATE TABLE #{table_name}")
    end
    
    puts "Creating items"
    100000.times.each do |i|
      name = SecureRandom.hex
      state = Random.rand(2)
      Tag.new(name: name, state: state).save!
      TagIndexName.new(name: name, state: state).save!
      TagIndexComposite.new(name: name, state: state).save!
      TagIndexState.new(name: name, state: state).save!
      if i > 0 && (i % 10000) == 0
        print i
      end
    end
    puts "Done creating items"
    
    iterations = 1
    table_names.each do |table_name|
      puts "Results for #{table_name}"
      Benchmark.bm do |bm|
        bm.report do
          iterations.times do
            ActiveRecord::Base.connection.execute("SELECT name, COUNT(name) count FROM #{table_name} WHERE state = 1 GROUP BY name ORDER BY count DESC LIMIT 7")
          end
        end
      end
    end
    

答案 2 :(得分:3)

对于此特定查询,最合适的索引是覆盖索引。

  CREATE INDEX Tags_IX1 ON Tags (state, name)

我们希望您的查询的EXPLAIN输出会显示正在使用的索引,使用&#34;使用索引&#34;在Extra列中,避免昂贵的&#34;使用filesort&#34;操作

因为在WHERE子句中state上有一个等式谓词,然后在name列上按操作分组,MySQL可以满足索引的查询,而不需要做一个&#34;排序&#34;操作,而不对基础表中的页面进行任何查找。

仅在name列上创建索引的建议(在其他答案中)不足以实现此特定查询的最佳性能。

如果我们创建了这样的索引:

  ... ON Tags (name,state)

name作为前导列,然后我们可以重新编写查询以更有效地使用该索引:

  SELECT t.name
       , SUM(IF(t.state='1',t.name IS NOT NULL,NULL) AS count
    FROM Tags t
   GROUP BY t.name
   ORDER BY count DESC
   LIMIT 7

修改

此处的其他答案建议在state列上添加索引。 state似乎可能具有较低的基数。也就是说,该列只有少量值,并且很大一部分行的值为'1'。在这种情况下,仅state的索引不太可能提供最佳性能。这是因为使用该索引(如果MySQL甚至使用它)将需要查找底层数据页以检索name列,然后需要对所有行进行排序以满足GROUP BY

使用EXPLAIN,Luke。

参考: 8.8.1使用EXPLAIN优化查询 https://dev.mysql.com/doc/refman/5.6/en/using-explain.html

<强>后续

@Allendar声称(在对此答案的评论中)这个答案是错误的。他说我推荐的覆盖指数&#34;不会提高性能&#34;并且说单列state上的索引(正如他的回答中所推荐的)是正确的答案。他还建议进行测试。

所以,这是一个测试。

SQL Fiddle Here: http://sqlfiddle.com/#!9/20e73/2

(耐心地打开SQL Fiddle链接......它填充了一百万行表,构建了四个索引,并运行了十五个查询,因此它旋转了十几秒。)

以下是我本地计算机上运行MySQL 5.6的结果:

run   no index     (state,name)  (name,state)  (state)      (name)
----  -----------  ------------  ------------  -----------  -----------
run1   2.410 sec    0.687 sec     1.076 sec     3.374 sec    3.924 sec
run2   2.433 sec    0.659 sec     1.074 sec     3.267 sec    3.958 sec
run3   2.851 sec    0.717 sec     1.024 sec     3.423 sec    4.222 sec
  • 最快的是(state,name)
  • 上的多列索引
  • 第二快的是(name,state)
  • 上的多列索引
  • 第三快是对表的全面扫描
  • 排在第四位,比表扫描慢,是(state)
  • 的索引
  • 最后,仅(name)
  • 的索引

从SQL Fiddle上运行,结果类似:

         none     (s,n)    (n,s)   (n)     (s)
 ----    ------   ------   ------  ------  ------
 run1     701ms    193ms    286ms  1462ms   959ms
 run2     707ms    191ms    282ms  1170ms   957ms
 run3     702ms    190ms    283ms  1157ms   914ms

测试结果表明(state,name)上的多列索引是赢家。

测试结果还表明,全表扫描更快比仅使用state列上的索引。也就是说,通过告诉MySQL 忽略state列上的索引,我们可以获得更好的性能。

答案 3 :(得分:1)

state字段上创建一个INDEX。这就是原因;

BTREE INDEX是在 search-queries (a.k.a. WHERE子句)的state字段上进行的。现在将会发生的事情是BTREE将为您的state值编制索引;

1 - &gt; 11 - &gt; 11 - &gt; 112

2 - &gt; 21 - &gt; 22 - &gt; 221

现在,如果有,请说100k的结果与state ID 1一致。它将询问BTREE INDEX分支并以1开头。它不需要更深入,因为它已经找到了它。在该分支下,它现在可以立即知道它所需的所有独特记录,并且根据您的状态查找名称将会非常快速。

供将来参考;如果您还在name state上执行了WHERE,那么您需要在namestate上进行组合索引,因此BTREE将结合它们的更复杂的INDEX并且也将改进这些查询。

希望这有帮助。

祝你好运!