在有序的范围查询上优化mysql索引

Wil*_*zer 5 mysql sql database

我正在寻找有关我目前针对我的服务器运行的一些令人反感的mysql查询的帮助.我的目标是显示最便宜的ebay项目,结束时间不到一个月.

我正在使用MySQL 5.1.

我的查询如下('ebay_items'有~35万行):

explain SELECT `ebay_items`.* FROM `ebay_items` 
WHERE (endtime > NOW()-INTERVAL 1 MONTH) ORDER BY price desc\G;
Run Code Online (Sandbox Code Playgroud)

收益率:

*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: ebay_items
type: range
possible_keys: endtime
key: endtime
key_len: 9
ref: NULL
rows: 71760
Extra: Using where; Using filesort
1 row in set (0.00 sec)
Run Code Online (Sandbox Code Playgroud)

此查询导致使用71760行的昂贵"文件排序".

show indexes on ebay_items;
Run Code Online (Sandbox Code Playgroud)

收益率(我只包括有问题的指数,'endtime'):

*************************** 7. row ***************************
Table: ebay_items
Non_unique: 1
Key_name: endtime
Seq_in_index: 1
Column_name: endtime
Collation: A
Cardinality: 230697
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment: 
*************************** 8. row ***************************
Table: ebay_items
Non_unique: 1
Key_name: endtime
Seq_in_index: 2
Column_name: price
Collation: A
Cardinality: 230697
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment: 
Run Code Online (Sandbox Code Playgroud)

仅使用复合结束时间索引(结束时间,价格)的"endtime"键.据我所知,在处理范围查询和"order by"子句时,MySQL不会有效地使用复合索引.

有没有人找到这个问题的良好锻炼?我主要想在数据库级别解决它(通过更智能地使用索引或模式更改),但我愿意接受建议.

我可以避免范围查询的一​​种方法是让后台任务循环每X小时,并将ebay_items上的枚举类型字段标记为"<1天大","<1周龄","<1个月大",我希望以更清洁的方式解决问题.

有没有办法用order by子句执行MySQL范围查询,以有效的方式查询?

非常感谢您的帮助!

编辑:KohányiRóbert提出了一个很好的观点,我应该澄清我对查询的确切问题.查询导致磁盘I/O被挂起持续时间.如果其中几个查询同时运行,则会备份进程并锁定计算机.我的假设是filesort正在吃掉I/O.

我还应该提到该表正在使用MyISAM引擎.使用InnoDB引擎会提高性能,减少I/O密集吗?再次感谢.

Koh*_*ert 8

介绍

我喜欢你的问题,所以我玩了一些MySQL,并试图找到问题的根源.为此,我创建了一些测试.

数据

我使用一个名为随机数据生成器的工具生成了100.000行样本数据(我认为文档有点过时,但它有效).我传递给的配置文件gendata.pl如下.

$tables = {
  rows => [100000],
  names => ['ebay_items'],
  engines => ['MyISAM'],
  pk => ['int auto_increment']
};

$fields = {
  types => ['datetime', 'int'],
  indexes => [undef]
};

$data = {
  numbers => [
    'tinyint unsigned', 
    'smallint unsigned', 
    'smallint unsigned',
    'mediumint unsigned'
  ],
  temporals => ['datetime']
}; 
Run Code Online (Sandbox Code Playgroud)

我已经运行了两个单独的测试批次:一个使用MyISAM表,另一个使用InnoDB.(所以基本上你在上面的片段中用InnoDB替换了MyISAM.)

该工具创建一个表,其中列被调用pk,col_datetimecol_int.我已将它们重命名为与您的表格列匹配.结果表就在下面.

+---------+----------+------+-----+---------+----------------+
| Field   | Type     | Null | Key | Default | Extra          |
+---------+----------+------+-----+---------+----------------+
| endtime | datetime | YES  | MUL | NULL    |                |
| id      | int(11)  | NO   | PRI | NULL    | auto_increment |
| price   | int(11)  | YES  | MUL | NULL    |                |
+---------+----------+------+-----+---------+----------------+
Run Code Online (Sandbox Code Playgroud)

指数

该工具不创建索引,因为我喜欢它手工创建它们.

CREATE INDEX `endtime` ON `ebay_items` (endtime, price);
CREATE INDEX `price` ON `ebay_items` (price, endtime);
CREATE INDEX `endtime_only` ON `ebay_items` (endtime);
CREATE INDEX `price_only` ON `ebay_items` (price);
Run Code Online (Sandbox Code Playgroud)

询问

我用过的查询.

SELECT `ebay_items`.* 
FROM `ebay_items`  
FORCE INDEX (`endtime|price|endtime_only|price_only`)
WHERE (`endtime` > '2009-01-01' - INTERVAL 1 MONTH) 
ORDER BY `price` DESC
Run Code Online (Sandbox Code Playgroud)

(使用其中一个索引的四种不同查询.我使用的是2009-01-01代替,NOW()因为该工具似乎在2009年左右生成日期.)

说明

以下是EXPLAINMyISAM(顶部)和InnoDB(底部)表中每个索引的上述查询的输出.

时间结束

           id: 1
  select_type: SIMPLE
        table: ebay_items
         type: range
possible_keys: endtime
          key: endtime
      key_len: 9
          ref: NULL
         rows: 25261
        Extra: Using where; Using filesort

           id: 1
  select_type: SIMPLE
        table: ebay_items
         type: range
possible_keys: endtime
          key: endtime
      key_len: 9
          ref: NULL
         rows: 21026
        Extra: Using where; Using index; Using filesort
Run Code Online (Sandbox Code Playgroud)

价钱

           id: 1
  select_type: SIMPLE
        table: ebay_items
         type: index
possible_keys: NULL
          key: price
      key_len: 14
          ref: NULL
         rows: 100000
        Extra: Using where

         id: 1
  select_type: SIMPLE
        table: ebay_items
         type: index
possible_keys: NULL
          key: price
      key_len: 14
          ref: NULL
         rows: 100226
        Extra: Using where; Using index
Run Code Online (Sandbox Code Playgroud)

endtime_only

           id: 1
  select_type: SIMPLE
        table: ebay_items
         type: range
possible_keys: endtime_only
          key: endtime_only
      key_len: 9
          ref: NULL
         rows: 11666
        Extra: Using where; Using filesort

          id: 1
  select_type: SIMPLE
        table: ebay_items
         type: range
possible_keys: endtime_only
          key: endtime_only
      key_len: 9
          ref: NULL
         rows: 21270
        Extra: Using where; Using filesort
Run Code Online (Sandbox Code Playgroud)

price_only

           id: 1
  select_type: SIMPLE
        table: ebay_items
         type: index
possible_keys: NULL
          key: price_only
      key_len: 5
          ref: NULL
         rows: 100000
        Extra: Using where

           id: 1
  select_type: SIMPLE
        table: ebay_items
         type: index
possible_keys: NULL
          key: price_only
      key_len: 5
          ref: NULL
         rows: 100226
        Extra: Using where
Run Code Online (Sandbox Code Playgroud)

基于这些我决定使用endtime_only索引进行测试,因为我不得不对MyISAM和InnoDB表运行查询.但正如你所看到的那样,最合乎逻辑的endtime指数似乎是最好的.

测试

为了使用MyISAM和InnoDB表测试查询的效率(关于生成的I/O活动),我编写了以下简单的Java程序.

static final String J = "jdbc:mysql://127.0.0.1:3306/test?user=root&password=root";
static final String Q = "SELECT * FROM ebay_items FORCE INDEX (endtime_only) WHERE (endtime > '2009-01-01'-INTERVAL 1 MONTH) ORDER BY price desc;";

public static void main(String[] args) throws InterruptedException {
  for (int i = 0; i < 1000; i++)
    try (Connection c = DriverManager.getConnection(J);
        Statement s = c.createStatement()) {
      TimeUnit.MILLISECONDS.sleep(10L);
      s.execute(Q);
    } catch (SQLException ex) {
      ex.printStackTrace();
    }
}
Run Code Online (Sandbox Code Playgroud)

建立

我在Dell Vostro 1015笔记本电脑上运行MySQL 5.5的Windows二进制文件,Intel Core Duo T6670 @ 2.20 GHz,4 GB RAM.Java程序通过TCP/IP与MySQL服务器进程通信.

mysqld使用MyISAM和InnoDB(使用Process Explorer)在对表运行测试之前和之后捕获了进程的状态.

之前

mysqld性能选项卡

mysqld磁盘和网络选项卡

之后 - MyISAM

mysqld性能选项卡/ MyISAM

mysqld磁盘和网络选项卡/ MyISAM

之后 - InnoDB

mysqld性能选项卡/ InnoDB

mysqld磁盘和网络选项卡/ InnoDB

结论

基本上,两次运行仅在单个I/O读取的数量上有所不同,当表格使用MyISAM引擎时,这非常大.两次测试都进行了50-60秒.在MyISAM引擎的情况下,CPU的最大负载大约为42%,而使用InnoDB时大约为38.

我不太确定大量I/O读取的含义是什么,但在这种情况下,更小的更好(可能).如果你的表中有更多的列(除了你指定的列之外)并且有一些非默认的MySQL配置(关于缓冲区大小等),MySQL可能会使用磁盘资源.