如何正确预热 MySQL FULLTEXT 索引?
How to properly warm a MySQL FULLTEXT index?
我在 Amazon RDS 上安装了 MySQL V5.6.23 运行ning。其中有一个名为 product_details
的 InnoDB table,它包含大约 10 列,这些列都针对精确匹配(日期、数字、文本等)进行了索引。然后我有一个 product_name
字段,我已经在上面放置了 FULLTEXT 索引。我还有很多我们不搜索的其他字段。
table目前有150M行,我们每晚添加大约3-5M,每晚还会更新另外10-20M。在晚上 运行 处理这些 inserts/updates 之后,全文索引似乎从内存中删除(不确定到底发生了什么)。
当我第一次 运行 查询 'blue ford taurus' 时,查询最多可能需要几分钟。第二次我运行它,不是几百毫秒就是几秒。如果我 运行 OPTIMIZE TABLE product_details;
在新数据处理完成后,那么几乎我测试的每个搜索都尽可能快。这需要几个小时才能 运行 OPTIMIZE TABLE
(因为我认为它正在重写整个 table (和索引?)?!?!
我曾考虑过创建一个 "warming" 脚本,该脚本将针对用户的常见查询发送 table,但我对正在发生的事情没有很好的心理模型,所以我不知道这会热身什么。搜索 'blue ford taurus' 似乎不仅加快了该查询的速度,但我不明白为什么。
问题
每天晚上加载新数据后,应该如何适当地预热这些索引?此 table 支持最终用户每天早上搜索它的网络应用程序。
我如何知道保存索引所需的内存要求?
评论
- 我打算将这一切转移到 Elasticsearch(或类似的),我在其中有很多搜索经验。我不熟悉 MySQL 作为 FULLTEXT "search engine," 但目前我坚持使用它。
普通查询
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;
Table
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;
Table状态
上面的 table 状态数据实际上是我的开发 table 的,其中只有 18M 行。当我加载所有生产数据时,它的数据量将增加约 8 倍,这意味着 data_length
将约为 70GB,而 index_length
将约为 32GB。
优化(或不优化)。是的 OPTIMIZE TABLE
复制 table 并重建所有索引,因此需要很长时间。不要运行OPTIMIZE
;它几乎没有帮助。 (或者您看到显着变化了吗?)
调整。你有多少内存?索引有多大? SHOW TABLE STATUS
.
innodb_buffer_pool_size
应该是 可用 RAM 的大约 70%。
缩小架构 会有一点帮助:
- 将
DATE
和 TIME
分成两个字段很少见好
- 为什么这个table里面同时有
city
和city_id
。也许您应该将 city
和 state
以及 zip_code
标准化为另一个 table(一个,而不是另外两个 table)。
- id 的大小应该适当——
city_id
可以是 SMALLINT UNSIGNED
(2 个字节:0..65535)而不是 INT SIGNED
(4 个字节)。
- 标准化
category_name
和任何其他重复的列?
updated_date
是一个 VARCHAR
??
您查询的步骤
- 找到同时具有 ipad 和 nano 的产品的所有 ID。假设有 5555 行这样的行。
- 去所有 5555 行,收集所需的信息,因为
*
,这是所有列。听起来 table 比 RAM 大很多,所以这意味着大约 5555 次磁盘读取——可能是最慢的部分。
- 根据
city_id
过滤掉不需要的行。假设我们减少到 3210 行。
- 将所有 3210 行的所有列写入 tmp table。由于有一个
TEXT
列,它将是一个 MyISAM table,而不是更快的 MEMORY table.
- 排序
timestamp
- 交付前 1000 个。
我希望您能看到,大行意味着 tmp 中的内容大 table。减少 *
and/or 收缩列。
这里有一个技巧 可以减少 tmp table 大小(步骤 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 table 并不是最糟糕的部分,这需要进行第二次排序。所以,你可以试试这个,但不要屏住呼吸。
请注意,当您运行一个测试时,很可能会I/O-bound,运行它两次。第二个 运行 将是一个更公平的比较,因为它大概没有 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.
- 大 table 只需要 1000 个探针。这应该大获全胜。
步骤:
- 从 FULLTEXT 索引中获取 5555 个 id。
- 使用 希望 成为内存操作的方式过滤到 3210 个 ID。
- 排序 3210 'narrow' 行(仅 3 列,不是全部)。这次可以是
MEMORY
tmp table.
JOIN
回原table仅1000次。 (大赢。)(我在这里可能是错的;它可能是 3210,但仍然比 5555 好。)
- 交付结果。
我在 Amazon RDS 上安装了 MySQL V5.6.23 运行ning。其中有一个名为 product_details
的 InnoDB table,它包含大约 10 列,这些列都针对精确匹配(日期、数字、文本等)进行了索引。然后我有一个 product_name
字段,我已经在上面放置了 FULLTEXT 索引。我还有很多我们不搜索的其他字段。
table目前有150M行,我们每晚添加大约3-5M,每晚还会更新另外10-20M。在晚上 运行 处理这些 inserts/updates 之后,全文索引似乎从内存中删除(不确定到底发生了什么)。
当我第一次 运行 查询 'blue ford taurus' 时,查询最多可能需要几分钟。第二次我运行它,不是几百毫秒就是几秒。如果我 运行 OPTIMIZE TABLE product_details;
在新数据处理完成后,那么几乎我测试的每个搜索都尽可能快。这需要几个小时才能 运行 OPTIMIZE TABLE
(因为我认为它正在重写整个 table (和索引?)?!?!
我曾考虑过创建一个 "warming" 脚本,该脚本将针对用户的常见查询发送 table,但我对正在发生的事情没有很好的心理模型,所以我不知道这会热身什么。搜索 'blue ford taurus' 似乎不仅加快了该查询的速度,但我不明白为什么。
问题
每天晚上加载新数据后,应该如何适当地预热这些索引?此 table 支持最终用户每天早上搜索它的网络应用程序。
我如何知道保存索引所需的内存要求?
评论
- 我打算将这一切转移到 Elasticsearch(或类似的),我在其中有很多搜索经验。我不熟悉 MySQL 作为 FULLTEXT "search engine," 但目前我坚持使用它。
普通查询
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;
Table
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;
Table状态
上面的 table 状态数据实际上是我的开发 table 的,其中只有 18M 行。当我加载所有生产数据时,它的数据量将增加约 8 倍,这意味着 data_length
将约为 70GB,而 index_length
将约为 32GB。
优化(或不优化)。是的 OPTIMIZE TABLE
复制 table 并重建所有索引,因此需要很长时间。不要运行OPTIMIZE
;它几乎没有帮助。 (或者您看到显着变化了吗?)
调整。你有多少内存?索引有多大? SHOW TABLE STATUS
.
innodb_buffer_pool_size
应该是 可用 RAM 的大约 70%。
缩小架构 会有一点帮助:
- 将
DATE
和TIME
分成两个字段很少见好 - 为什么这个table里面同时有
city
和city_id
。也许您应该将city
和state
以及zip_code
标准化为另一个 table(一个,而不是另外两个 table)。 - id 的大小应该适当——
city_id
可以是SMALLINT UNSIGNED
(2 个字节:0..65535)而不是INT SIGNED
(4 个字节)。 - 标准化
category_name
和任何其他重复的列? updated_date
是一个VARCHAR
??
您查询的步骤
- 找到同时具有 ipad 和 nano 的产品的所有 ID。假设有 5555 行这样的行。
- 去所有 5555 行,收集所需的信息,因为
*
,这是所有列。听起来 table 比 RAM 大很多,所以这意味着大约 5555 次磁盘读取——可能是最慢的部分。 - 根据
city_id
过滤掉不需要的行。假设我们减少到 3210 行。 - 将所有 3210 行的所有列写入 tmp table。由于有一个
TEXT
列,它将是一个 MyISAM table,而不是更快的 MEMORY table. - 排序
timestamp
- 交付前 1000 个。
我希望您能看到,大行意味着 tmp 中的内容大 table。减少 *
and/or 收缩列。
这里有一个技巧 可以减少 tmp table 大小(步骤 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 table 并不是最糟糕的部分,这需要进行第二次排序。所以,你可以试试这个,但不要屏住呼吸。
请注意,当您运行一个测试时,很可能会I/O-bound,运行它两次。第二个 运行 将是一个更公平的比较,因为它大概没有 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. - 大 table 只需要 1000 个探针。这应该大获全胜。
步骤:
- 从 FULLTEXT 索引中获取 5555 个 id。
- 使用 希望 成为内存操作的方式过滤到 3210 个 ID。
- 排序 3210 'narrow' 行(仅 3 列,不是全部)。这次可以是
MEMORY
tmp table. JOIN
回原table仅1000次。 (大赢。)(我在这里可能是错的;它可能是 3210,但仍然比 5555 好。)- 交付结果。