【问题标题】:How to properly warm a MySQL FULLTEXT index?如何正确加热 MySQL FULLTEXT 索引?
【发布时间】:2016-03-12 00:29:21
【问题描述】:

我在 Amazon RDS 上运行了 MySQL V5.6.23。其中有一个名为product_details 的 InnoDB 表,其中包含大约 10 个列,这些列都为精确匹配(日期、数字、文本等)编制了索引。然后我有一个单独的product_name 字段,我已经在该字段上放置了 FULLTEXT 索引。我还有很多其他领域我们没有搜索。

该表目前有 150M 行,我们每晚增加大约 3-5M,并且每晚更新另外 10-20M。在晚上运行这些插入/更新后,FULLTEXT 索引似乎从内存中删除(不确定这到底是怎么回事)。

当我第一次运行“blue ford taurus”的查询时,查询可能需要几分钟时间。我第二次运行它时,即使不是几百毫秒,也只有几秒钟。如果我在新数据处理完成后运行OPTIMIZE TABLE product_details;,那么我测试的几乎每个搜索都尽可能快。这需要几个小时才能运行OPTIMIZE TABLE(因为我认为它正在重写整个表(和索引?)?!?!

我曾考虑过创建一个“预热”脚本,该脚本只会针对来自用户的常见查询进行处理,但我对正在发生的事情没有很好的心理模型,所以我不知道这是什么会热身。搜索“blue ford taurus”似乎不仅加快了查询速度,但我不明白为什么。

问题

  1. 在每晚加载新数据后,如何正确加热这些索引?此表支持最终用户每天早上搜索的 Web 应用程序。

  2. 我如何知道保存索引需要哪些内存要求?

评论

  1. 我正计划将这一切转移到 Elasticsearch(或类似的),在那里我有很多搜索经验。我不熟悉 MySQL 作为 FULLTEXT“搜索引擎”,但目前我还是坚持使用它。

常见查询

SELECT * FROM product_details as pd 
WHERE
    MATCH (pd.product_name) AGAINST ('+ipod +nano' IN BOOLEAN MODE)
    and pd.city_id IN (577,528,567,614,615,616,618) 
ORDER BY(pd.timestamp) DESC
LIMIT 1000;

表格

CREATE TABLE `product_details` (
  `product_name` text NOT NULL,
  `category_name` varchar(100) NOT NULL,
  `product_description` text NOT NULL,
  `price` int(11) NOT NULL,
  `address` varchar(200) NOT NULL,
  `zip_code` varchar(30) NOT NULL DEFAULT '',
  `phone` bigint(10) DEFAULT NULL,
  `email` varchar(50) NOT NULL,
  `state` varchar(20) NOT NULL,
  `city` varchar(30) NOT NULL,
  `post_id` bigint(11) NOT NULL,
  `post_date` date DEFAULT NULL,
  `post_time` time NOT NULL,
  `updated_date` varchar(10) NOT NULL,
  `updated_time` time NOT NULL,
  `status` tinyint(4) NOT NULL,
  `timestamp` date NOT NULL,
  `new_field` tinyint(4) DEFAULT NULL,
  `multiple_items` tinyint(1) NOT NULL,
  `city_id` int(4) NOT NULL,
  `date_changed` date DEFAULT NULL,
  `latlong` varchar(100) NOT NULL,
  PRIMARY KEY (`post_id`),
  KEY `city_id` (`city_id`),
  KEY `post_date` (`post_date`),
  KEY `price` (`price`),
  KEY `category_name` (`category_name`),
  KEY `state` (`state`),
  KEY `multiple_items` (`multiple_items`),
  KEY `new_field` (`new_field`),
  KEY `phone` (`phone`),
  KEY `timestamp` (`timestamp`),
  KEY `date_changed` (`date_changed`),
  FULLTEXT KEY `product_name` (`product_name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

表格状态

上面的表状态数据实际上是针对我的 dev 表的,其中只有 18M 行。当我加载所有生产数据时,它将有 ~8 倍的数据量,这意味着 data_length 将是 ~70GB,index_length 将是 ~32GB。

【问题讨论】:

    标签: mysql indexing full-text-search innodb full-text-indexing


    【解决方案1】:

    优化(或不优化)。是的OPTIMIZE TABLE 复制表并重建所有索引,因此需要很长时间。不要运行OPTIMIZE;它几乎没有帮助。 (或者您是否看到了重大变化?)

    调整。你有多少内存?索引有多大? SHOW TABLE STATUS.

    innodb_buffer_pool_size 应该是大约 70% 的可用 RAM。

    缩小架构会有所帮助:

    • DATETIME 拆分为两个字段很少见
    • 为什么citycity_id 在此表中。也许您应该将 citystate 以及 zip_code 标准化到另一个表(一个,而不是另外两个表)。
    • id 的大小应该适当——city_id 可以是SMALLINT UNSIGNED(2 个字节:0..65535)而不是INT SIGNED(4 个字节)。
    • 规范化category_name 和任何其他重复的列?
    • updated_dateVARCHAR??

    查询中的步骤

    1. 找到所有带有 ipad 和 nano 的产品的 ID。假设有 5555 行这样的行。
    2. 去所有 5555 行,收集所需的信息,这是因为 * 的所有列。听起来该表比 RAM 大得多,所以这意味着大约 5555 次磁盘读取——可能是最慢的部分。
    3. 根据city_id 过滤掉不需要的行。假设我们减少到 3210 行。
    4. 将所有 3210 行的所有列写入 tmp 表。由于有一个 TEXT 列,它将是一个 MyISAM 表,而不是一个更快的 MEMORY 表。
    5. timestamp排序
    6. 交付前 1000 个。

    我希望您能看到,庞大的行意味着 tmp 表中的庞大内容。减少* 和/或缩小列。

    这里有一个技巧来减少 tmp 表的大小(步骤 4、5、6):

    SELECT  ...
        FROM  product_details as pd
        JOIN  
          ( SELECT  post_id
                FROM  product_details
                WHERE  MATCH (product_name) AGAINST ('+ipod +nano' IN BOOLEAN MODE)
                  and  city_id IN (577,528,567,614,615,616,618)
                ORDER BY timestamp DESC
                LIMIT  1000
          ) x USING (post_id)
        ORDER BY  pd.timestamp;
    

    但是,tmp 表并不是最糟糕的部分,这需要进行第二次排序。所以,你可以试试这个,但不要屏住呼吸。

    请注意,当您运行可能受 I/O 限制的测试时,请运行两次。第二次运行将是一个更公平的比较,因为它可能没有 I/O。

    另一层应该更快

    SELECT  pd...
        FROM  
          ( SELECT  post_id
                FROM  product_details
                WHERE  MATCH (product_name) AGAINST ('+ipod +nano' IN BOOLEAN MODE) 
          ) AS a
        JOIN  product_details AS b ON b.post_id = a.post_id
        WHERE  b.city_id IN (577,528,567,614,615,616,618)
        ORDER BY  b.timestamp DESC
        LIMIT  1000 ) x
        JOIN  product_details as pd ON pd.post_id = b.post_id
        ORDER BY  pd.timestamp;
    
    INDEX(post_id, city_id, timestamp) -- also required for this formulation
    

    这个公式的希望是

    • 二次过滤 (city_id) 在更小的 BTree(该索引)上完成,因此更有可能存在于 RAM 中,从而避免了 I/O。
    • 大表中只需要 1000 个探针。这应该是一个巨大的胜利。

    步骤:

    1. 从 FULLTEXT 索引中获取 5555 个 ID。
    2. 使用希望在内存中的操作过滤到 3210 个 ID。
    3. 对 3210 个“窄”行(仅 3 列,不是全部)进行排序。这次可以是MEMORY tmp 表。
    4. JOIN 只回原表1000次。 (大获全胜。)(我可能错了;可能是 3210,但仍然比 5555 好。)
    5. 交付结果。

    【讨论】:

    • 我在我的问题中添加了table status 数据。这对快速查询的 RAM 要求有何影响?你有没有想过在用户进入系统之前在晚上加热FREETEXT 索引?这个系统中的很多杂乱无章都是遗留问题,我正试图让一个损坏的站点在我们重建整个系统的同时再次运行几个月。很棒的建议。谢谢!!
    • 如果您不重新启动系统并且不在一夜之间进行大查询,那么您应该不需要“准备缓存”。是的,运行一些有代表性的查询可能会有所帮助。但这只会帮助这样的查询。我的建议试图帮助所有查询。
    • 如果你有 16GB 的 RAM,你可能有很多 I/O 正在进行。 (并且您的 cmets 暗示您的 RAM 很小。)如果您有超过 100GB 的 RAM,那么我对减速感到困惑。您还有其他“忙碌”的大桌子吗?
    • 我目前有 16GB 的内存。为了使查询快速,我需要有足够的内存来做什么?如果 table_status index_length 为 32GB,我是否需要 32GB 内存(可能更像 40GB)?什么将索引加载到内存中?我正试图绕过一些步骤。如果我重新启动服务器,内存被清除并且查询很慢。是什么让这些索引重新进入内存?我可以强制系统加载它们吗?
    • 您尝试过重新制定的查询吗?至于 RAM - 这取决于您查找了多少 不同 术语,以及您需要访问多少其他数据等。对索引的 5555 次命中可能比对数据的 3210 次命中少 I/O。为 Data_length + Index_length 提供足够的 RAM 会更安全。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-26
    • 2018-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-05
    相关资源
    最近更新 更多