将字符串列存储在索引中是否切实可行?

时间:2013-05-29 04:52:53

标签: mysql indexing innodb

假设我们有这个结构/数据示例:

@see http://sqlfiddle.com/#!8/1f85e/1

-- SET GLOBAL innodb_file_per_table=1;
DROP TABLE IF EXISTS mysql_index_reading_myisam;
CREATE TABLE IF NOT EXISTS mysql_index_reading_myisam (
    id INT NOT NULL AUTO_INCREMENT
  , str VARCHAR(50) NOT NULL
  , enm ENUM('thatis', 'thequestion') NOT NULL
  , cnt TINYINT NOT NULL

  , PRIMARY KEY (id)
  , INDEX str_cnt (str, cnt)
  , INDEX enm_cnt (enm, cnt)

) ENGINE=MyISAM CHARSET=Latin1;
INSERT INTO mysql_index_reading_myisam (str, enm, cnt) VALUES
    ('Tobeornottobe', 'Thatis', 1)
  , ('toBeornottobe', 'thatIs', 2)
  , ('tobeOrnottobe', 'ThatIs', 3)
  , ('tobeorNottobe', 'thatis', 4)
  , ('tobeornotTobe', 'THATIS', 5)
;
DROP TABLE IF EXISTS mysql_index_reading_innodb;
CREATE TABLE mysql_index_reading_innodb LIKE mysql_index_reading_myisam;
ALTER TABLE mysql_index_reading_innodb ENGINE InnoDB;
INSERT INTO mysql_index_reading_innodb SELECT * FROM mysql_index_reading_myisam;

EXPLAIN SELECT cnt FROM mysql_index_reading_myisam WHERE str = 'tobeornottobe';
EXPLAIN SELECT cnt FROM mysql_index_reading_innodb WHERE str = 'tobeornottobe';
EXPLAIN SELECT cnt FROM mysql_index_reading_myisam WHERE enm = 'thatis';
EXPLAIN SELECT cnt FROM mysql_index_reading_innodb WHERE enm = 'thatis';

让我们检查它在内部的存储方式

# egrep --ignore-case --only-matching --text '(tobeornottobe|thatis)' *
mysql_index_reading_innodb.frm:thatis
mysql_index_reading_innodb.ibd:Tobeornottobe
mysql_index_reading_innodb.ibd:toBeornottobe
mysql_index_reading_innodb.ibd:tobeOrnottobe
mysql_index_reading_innodb.ibd:tobeorNottobe
mysql_index_reading_innodb.ibd:tobeornotTobe
mysql_index_reading_innodb.ibd:Tobeornottobe
mysql_index_reading_innodb.ibd:toBeornottobe
mysql_index_reading_innodb.ibd:tobeOrnottobe
mysql_index_reading_innodb.ibd:tobeorNottobe
mysql_index_reading_innodb.ibd:tobeornotTobe
mysql_index_reading_myisam.frm:thatis
mysql_index_reading_myisam.MYD:Tobeornottobe
mysql_index_reading_myisam.MYD:toBeornottobe
mysql_index_reading_myisam.MYD:tobeOrnottobe
mysql_index_reading_myisam.MYD:tobeorNottobe
mysql_index_reading_myisam.MYD:tobeornotTobe
mysql_index_reading_myisam.MYI:Tobeornottobe
mysql_index_reading_myisam.MYI:toBeornottobe
  • 在两个引擎中,枚举都存储在* .frm中。确定。
  • 在两个引擎中存储数据和数据/索引文件的数据。确定。
  • 在MyISAM索引中有两条记录。
  • 在InnoDB索引中,所有五个记录都是正确的。

我已经找到了什么

http://dev.mysql.com/doc/refman/5.1/en/mysql-indexes.html

  

在某些情况下,可以优化查询以在不使用的情况下检索值   咨询数据行。如果查询仅使用表中的列   这是数字,并形成一些键的最左前缀,   可以从索引树中检索所选值以获得更大的值   速度:

     

SELECT key_part3 FROM tbl_name WHERE key_part1 = 1

http://www.mysqlperformanceblog.com/2009/09/12/3-ways-mysql-uses-indexes/

  

使用索引读取数据一些存储引擎(MyISAM和Innodb   包括)也可以使用索引来读取数据,从而避免阅读   行数据本身。这不仅仅是每次读取2次的节省   索引条目而不是一个,但它可以节省IO数量级   某些情况 - 索引是排序的(至少在页面边界上)所以   在进行索引范围扫描时,您通常会从中获取许多索引条目   相同的页面,但行本身可以分散在许多页面上   可能需要大量的IO。最重要的是,如果你只是需要   访问几列索引可以简单地小得多   数据是覆盖索引的原因之一,有助于加快速度   查询即使数据在内存中。如果MySQL只读取索引和   不访问行,您将在EXPLAIN输出中看到“使用索引”。

然后在sql_select.cc的源代码中: http://bazaar.launchpad.net/~mysql/mysql-server/5.1/view/head:/sql/sql_select.cc#L12834

/*
  We can remove binary fields and numerical fields except float,
  as float comparison isn't 100 % secure
  We have to keep normal strings to be able to check for end spaces
*/
if (field->binary() &&
    field->real_type() != MYSQL_TYPE_STRING &&
    field->real_type() != MYSQL_TYPE_VARCHAR &&
    (field->type() != MYSQL_TYPE_FLOAT || field->decimals() == 0))
{
  return !store_val_in_field(field, right_item, CHECK_FIELD_WARN);
}

所以我的问题是

  1. 存储在索引字符串列中是否切实可行,只需要作为数据? 例如,包含20列的表,我们经常需要strcolumn,由intcolumn搜索。 创建像(intcolumn,strcolumn)这样的索引或者我们真的只需要(intcolumn)吗?

  2. innodb引擎中的mysql是否真的为其做了一些额外的操作 检索数据(当我们看到“使用where;使用索引”时)?

  3. ENUM也是如此。它发生了,因为Enum_field的 real_type返回MYSQL_TYPE_STRING。枚举是否也这样做?

  4. 我们可以假设,枚举是超级邪恶的,我们应该永远 只使用简单的参考表?

  5. 对于MyISAM来说,它是不可取的,因为它存储在索引中而不是所有值。 但是为什么它会存储两个值 - 而不是一个?

  6. 如果这一切真的发生了 - 它只是当前的重新发现 mysql内核,不依赖于具体的处理程序实现?

  7. ps:我看到这个问题很重要。如果有人会帮忙     重新制定/打破它 - 它会很好。


    Update1:​​添加另一个关于“使用索引”和“使用索引;使用where”

    的SQL

    @see http://sqlfiddle.com/#!8/3f287/2

    DROP TABLE IF EXISTS tab;
    CREATE TABLE IF NOT EXISTS tab (
        id INT NOT NULL AUTO_INCREMENT
      , num1 TINYINT NOT NULL
      , num2 TINYINT
      , str3 CHAR(1) NOT NULL
    
      , PRIMARY KEY (id)
      , INDEX num1_num2 (num1, num2)
      , INDEX num1_str3 (num1, str3)
      , INDEX num2_num1 (num2, num1)
      , INDEX str3_num1 (str3, num1)
    
    ) ENGINE=InnoDB;
    INSERT INTO tab (num1, num2, str3) VALUES
        (1, 1, '1')
      , (2, 2, '2')
      , (3, 3, '3')
      , (4, 4, '4')
      , (5, 5, '5')
      , (6, 6, '6')
      , (7, 7, '7')
      , (8, 8, '8')
      , (9, 9, '9')
      , (0, 0, '0')
    ;
    INSERT INTO tab (num1, num2, str3) SELECT num1, num2, str3 FROM tab;
    
    -- Using index
    EXPLAIN SELECT num2 FROM tab WHERE num1 =  5;
    EXPLAIN SELECT str3 FROM tab WHERE num1 =  5;
    -- Using where; Using index
    EXPLAIN SELECT num1 FROM tab WHERE num2 =  5;
    EXPLAIN SELECT num1 FROM tab WHERE str3 = '5';
    

    问题#2

    1. 为什么在非null int搜索的情况下,我们只看到“使用索引”?

    2. 但是如果是nullable int OR string - 我们也看到“使用where”?

    3. mysql做了哪些其他操作?

1 个答案:

答案 0 :(得分:7)

  1.   

    存储在索引字符串列中是否可行,只需要作为数据?例如,包含20列的表,我们经常需要strcolumn,由intcolumn搜索。创建像(intcolumn,strcolumn)这样的索引或者我们真的只需要(intcolumn)吗?

    这被称为覆盖索引;它具有能够从索引文件中检索所选列而不必从表数据中的记录中查找值的性能优势。

    与所有事情一样,它的使用是一种权衡,在某些情况下可能是适当的,但在其他情况下却不适用。

  2.   

    innodb引擎中的mysql是否真的为检索数据做了一些额外的操作(当我们看到“使用where;使用索引”时)?

    您的问题链接的sqlfiddle显示Using where; Using index所有四个查询。正如EXPLAIN Extra Information所述:

      

    EXPLAIN输出的Extra列包含有关MySQL如何解析查询的其他信息。以下列表说明了此列中可能出现的值。

    [ deletia ]
         
        
    • Using index

           

      仅使用索引树中的信息从表中检索列信息,而无需执行额外的搜索来读取实际行。当查询仅使用属于单个索引的列时,可以使用此策略。

           

      如果Extra列也显示Using where,则表示该索引用于执行键值的查找。如果没有Using where,优化器可能正在读取索引以避免读取数据行但不使用它进行查找。例如,如果索引是查询的覆盖索引,则优化程序可以扫描它而不使用它进行查找。

    •   

    因此,所有的查询都使用覆盖索引进行查找和数据检索,而不管使用的存储引擎如何。

    当你说“ innodb引擎确实为检索数据做了一些额外的操作”时,我不清楚你所指的是什么。我可以看到EXPLAIN输出的唯一区别是InnoDB查询在Rows列中显示 lower 值;但是,as documented

      

    rows列表示MySQL认为必须检查以执行查询的行数。

         

    对于InnoDB表,此数字是估算值,可能并不总是准确的。

  3.   

    ENUM也是如此。它发生了,因为Enum_field的real_type返回MYSQL_TYPE_STRING。枚举是否也这样做?

    同样,当你说“同样发生”时,我不清楚你所指的是什么。但是,如上所述,Using where; Using index仅表示覆盖索引已用于查找和数据检索。

    此外,ENUM字段的real_typeMYSQL_TYPE_ENUM,而不是MYSQL_TYPE_STRING。见sql/field.h:1873

      enum_field_types real_type() const { return MYSQL_TYPE_ENUM; }
    
  4.   

    我们可以假设,枚举是超级恶,我们应该总是使用简单的参考表吗?

    many reasons要避免ENUM,但我认为您的问题没有触及其中任何一个。

  5.   

    对于MyISAM来说,它是不可取的,因为它存储在索引中而不是所有值。但那为什么它会存储两个值 - 而不是一个?

    egrep结果导致您得出错误的结论。仅仅因为对模式"tobeornottobe"的不区分大小写的搜索在.myi文件中找到两个匹配的字符串意味着MyISAM索引有两个记录。数据结构是一棵树,如下所示:

                  /\
                 /  \
    Tobeornottobe    toBeornottobe
                       /\
                      /  \
         tobeOrnottobe    tobeorNottobe
                           \
                            \
                             tobeornotTobe
    

    通过查看所有字符串.myi索引文件:

    ,可以看到这一点
    $ strings mysql_index_reading_myisam.MYI
    Tobeornottobe
    toBeornottobe
    beOrnottobe
    orNottobe
    notTobe
    

    因此,如果您对模式"nottobe"执行(不区分大小写)搜索,您将找到五个匹配而不是两个匹配。

    您可以在The .MYI File中详细了解MyISAM索引结构的存储格式。

  6.   

    如果这一切都真的发生了 - 它只是当前的mysql内核的重构,那不依赖于具体的处理程序实现吗?

    我担心我不知道这里有什么问题。