【问题标题】:Is it practical to store string columns in indexes?将字符串列存储在索引中是否可行?
【发布时间】:2013-05-24 05:35:33
【问题描述】:

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

@see fiddle at 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 索引中,所有 5 条记录的大小写均正确。

我已经找到了什么

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 是否真的为 检索数据(当我们看到“Using where; Using index”时)?

  3. ENUM 也是如此。它发生了,因为 Enum_field`s real_type 返回 MYSQL_TYPE_STRING。枚举也一样吗?

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

  5. 对于 MyISAM,它是不可理解的,因为它在索引中存储的不是所有值。 但是为什么它会存储两个值——不是一个?

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

ps:我看到这个问题很大。如果有人会帮助 重新制定/打破它——这会很好。


更新 1:添加另一个关于“使用索引”与“使用索引;使用 where”的 SQL

@see fiddle at 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. 但在可为空的 int OR 字符串的情况下——我们还会看到“在哪里使用”?

  3. mysql 在那里做了哪些附加操作?

【问题讨论】:

    标签: mysql indexing innodb


    【解决方案1】:
    1. 将只需要作为数据的字符串列存储在索引中是否可行?例如有20列的表,我们经常需要strcolumn,即通过intcolumn搜索。创建像 (intcolumn,strcolumn) 这样的索引好还是我们真的只需要 (intcolumn) 在这里?

      这被称为覆盖索引;它的性能优势在于能够从索引文件中检索选定的列,而无需从表数据的记录中查找值。

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

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

      您的问题链接到的 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 /\ / \ tobeornottobeorNottobe \ \ 生存还是毁灭

      通过查看所有字符串 .myi 索引文件可以得到提示:

      $ 字符串 mysql_index_reading_myisam.MYI 生存还是毁灭 生存还是毁灭 成为或不成为 或Nottobe 不是托比

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

      你可以在The .MYI File阅读更多关于MyISAM的索引结构的存储格式。

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

      恐怕我不知道这里问的是什么。

    【讨论】:

    • 谢谢!关于“解释额外信息”。我已经看过一百万次了,但我认为有些事情并没有正确理解。
    • "列信息...仅索引中的信息"。 “列信息”是什么意思?是关于列类型/值/其他的元数据吗?
    • "...用于执行键值查找..."。也不清楚这里的查找和键值是什么。这是否意味着仅从索引中获取值?或者通过查找我们了解到,即使值存储在索引中,系统无论查看该行,但只知道在哪里查看。没有?
    • 我只是无法理解主要的事情——为什么在 int1value_int2value 索引的情况下我们只看到“使用索引”,但在 strvalue_intvalue 索引的情况下我们看到那些额外的“在哪里使用”!当系统通过 index 中的字符串值获取我们的 int 值时,系统还会做什么?
    • @gaRex:“列信息”指的是实际值(元数据在.frm文件中)。 “用于查找键值”是指使用索引过滤记录(通过遍历索引树)。您还没有显示在(int, int) 索引上查询的示例,它导致Using index 没有Using where,但是正如已经引用的那样:“如果没有Using where,优化器可能正在读取索引以避免读取数据行但不使用它进行查找。例如,如果索引是查询的覆盖索引,则优化器可能会扫描它而不使用它进行查找。"
    猜你喜欢
    • 1970-01-01
    • 2012-10-01
    • 1970-01-01
    • 2023-02-13
    • 1970-01-01
    • 2019-09-14
    • 1970-01-01
    • 2012-04-17
    • 1970-01-01
    相关资源
    最近更新 更多