MySQL在巨大的表和简单的SELECT上需要FORCE INDEX


8

我们有一个应用程序,它将来自不同来源的文章存储在MySQL表中,并允许用户检索按日期排序的那些文章。文章始终按来源进行过滤,因此对于客户端SELECT,我们始终有

WHERE source_id IN (...,...) ORDER BY date DESC/ASC

我们正在使用IN,因为用户有很多订阅(有些订阅有数千个)。

这是articles表的架构:

CREATE TABLE `articles` (
  `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  `source_id` INTEGER(11) UNSIGNED NOT NULL,
  `date` DOUBLE(16,6) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `source_id_date` (`source_id`, `date`),
  KEY `date` (`date`)
)ENGINE=InnoDB
AUTO_INCREMENT=1
CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
COMMENT='';

我们需要(date)索引,因为有时我们在此表上运行后台操作,而没有按源进行过滤。但是,用户无法执行此操作。

该表有大约10亿条记录(是的,我们正在考虑分片以便将来...)。一个典型的查询如下所示:

SELECT a.id, a.date, s.name
FROM articles a FORCE INDEX (source_id_date)
     JOIN sources s ON s.id = a.source_id
WHERE a.source_id IN (1,2,3,...)
ORDER BY a.date DESC
LIMIT 10

为什么要强制索引?因为事实证明,MySQL有时选择使用(date)索引进行此类查询(可能是因为它的长度更短?),这导致扫描数百万条记录。如果我们在生产中删除FORCE INDEX,我们的数据库服务器CPU核心将在数秒内耗尽(这是OLTP应用程序,上面的查询以每秒2000左右的速度执行)。

这种方法的问题在于,某些查询(我们怀疑它与IN子句中的source_id的数量有关)实际上与日期索引一起运行得更快。当对它们运行EXPLAIN时,我们看到source_id_date索引扫描数千万条记录,而date索引仅扫描数千条记录。通常情况相反,但是我们找不到牢固的关系。

理想情况下,我们想找出为什么MySQL优化器选择错误的索引并删除FORCE INDEX语句,但是一种预测何时强制日期索引的方法也对我们有用。

一些说明:

出于此问题的目的,上面的SELECT查询已大大简化。它对表有多个JOIN,每个表约有1亿行,并加入了PK(articles_user_flags.id = article.id),当要对数百万行进行排序时,问题更加严重。此外,某些查询还有其他位置,例如:

SELECT a.id, a.date, s.name
FROM articles a FORCE INDEX (source_id_date)
     JOIN sources s ON s.id = a.source_id
     LEFT JOIN articles_user_flags auf ON auf.article_id=a.id AND auf.user_id=1
WHERE a.source_id IN (1,2,3,...)
AND auf.starred=1
ORDER BY a.date DESC
LIMIT 10

该查询仅列出特定用户的已加星标的文章(1)。

服务器正在使用XtraDB运行MySQL 5.5.32(Percona)版本。硬件是2xE5-2620、128GB RAM,4HDDx1TB RAID10(具有电池支持的控制器)。有问题的SELECT完全受CPU限制。

my.cnf如下(删除了一些不相关的指令,例如server-id,port等):

transaction-isolation           = READ-COMMITTED
binlog_cache_size               = 256K
max_connections                 = 2500
max_user_connections            = 2000
back_log                        = 2048
thread_concurrency              = 12
max_allowed_packet              = 32M
sort_buffer_size                = 256K
read_buffer_size                = 128K
read_rnd_buffer_size            = 256K
join_buffer_size                = 8M
myisam_sort_buffer_size         = 8M
query_cache_limit               = 1M
query_cache_size                = 0
query_cache_type                = 0
key_buffer                      = 10M
table_cache                     = 10000
thread_stack                    = 256K
thread_cache_size               = 100
tmp_table_size                  = 256M
max_heap_table_size             = 4G
query_cache_min_res_unit        = 1K
slow-query-log                  = 1
slow-query-log-file             = /mysql_database/log/mysql-slow.log
long_query_time                 = 1
general_log                     = 0
general_log_file                = /mysql_database/log/mysql-general.log
log_error                       = /mysql_database/log/mysql.log
character-set-server            = utf8

innodb_flush_method             = O_DIRECT
innodb_flush_log_at_trx_commit  = 2
innodb_buffer_pool_size         = 105G
innodb_buffer_pool_instances    = 32
innodb_log_file_size            = 1G
innodb_log_buffer_size          = 16M
innodb_thread_concurrency       = 25
innodb_file_per_table           = 1

#percona specific
innodb_buffer_pool_restore_at_startup           = 60

根据要求,以下是一些有问题的查询的解释:

mysql> EXPLAIN SELECT a.id,a.date AS date_double
    -> FROM articles a
    -> FORCE INDEX (source_id_date)
    -> JOIN sources s ON s.id = a.source_id WHERE
    -> a.source_id IN (...) --Around 1000 IDs
    -> ORDER BY a.date LIMIT 20;
+----+-------------+-------+--------+-----------------+----------------+---------+---------------------------+----------+------------------------------------------+
| id | select_type | table | type   | possible_keys   | key            | key_len | ref                       | rows     | Extra                                    |
+----+-------------+-------+--------+-----------------+----------------+---------+---------------------------+----------+------------------------------------------+
|  1 | SIMPLE      | a     | range  | source_id_date  | source_id_date | 4       | NULL                      | 13744277 | Using where; Using index; Using filesort |
|  1 | SIMPLE      | s     | eq_ref | PRIMARY         | PRIMARY        | 4       | articles_db.a.source_id   |        1 | Using where; Using index                 |
+----+-------------+-------+--------+-----------------+----------------+---------+---------------------------+----------+------------------------------------------+
2 rows in set (0.01 sec)

实际的SELECT大约需要一分钟,并且完全受CPU限制。当我将索引更改为(date)时,在这种情况下,MySQL优化器也会自动选择:

mysql> EXPLAIN SELECT a.id,a.date AS date_double
    -> FROM articles a
    -> FORCE INDEX (date)
    -> JOIN sources s ON s.id = a.source_id WHERE
    -> a.source_id IN (...) --Around 1000 IDs
    -> ORDER BY a.date LIMIT 20;

+----+-------------+-------+--------+---------------+---------+---------+---------------------------+------+--------------------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref                       | rows | Extra                    |
+----+-------------+-------+--------+---------------+---------+---------+---------------------------+------+--------------------------+
|  1 | SIMPLE      | a     | index  | NULL          | date    | 8       | NULL                      |   20 | Using where              |
|  1 | SIMPLE      | s     | eq_ref | PRIMARY       | PRIMARY | 4       | articles_db.a.source_id   |    1 | Using where; Using index |
+----+-------------+-------+--------+---------------+---------+---------+---------------------------+------+--------------------------+

2 rows in set (0.01 sec)

SELECT只需10毫秒。

但是这里的解释可能很多!例如,如果我在IN子句中解释一个仅包含一个source_id的查询,并在(日期)上强制索引,则它告诉我它将仅扫描20行,但这是不可能的,因为该表具有超过10亿行并且只有很少的几行匹配此source_id。


“当我们对这些进行分析时……”EXPLAIN什么意思? ANALYZE则有所不同,如果没有,则可能要考虑,因为一种可能的解释是,偏斜的索引统计信息会分散优化器的明智选择。我认为问题中不需要my.cnf,最好使用该空间来发布EXPLAIN行为变化的一些输出,这些行为在您调查之后ANALYZE [LOCAL] TABLE...
Michael-sqlbot

是的,这是一个错字,感谢您的纠正。我已经解决了。当然,我们进行了ANALYZE,但这完全没有帮助。稍后,我将尝试捕获一些解释。
夹克

而且dateDOUBLE...?
ypercubeᵀᴹ

是的,因为这里需要微秒精度。该表的插入率约为每小时40万个条目,我们需要日期尽可能唯一。
夹克

@Jacket您可以在违规查询中发布EXPLAIN吗?我想是因为它是CPU绑定你的服务器quicksorting(“使用文件排序的说明)”您的结果集..
雷蒙德Nijland

Answers:


4

您可以检查innodb_stats_sample_pages参数的值。它控制着更新索引统计信息时MySQL对一个表执行多少次索引潜水,这些索引反过来又用于计算候选联接计划的成本。我们使用的版本的默认值为8。我们将其更改为128,并观察到较少的意外加入计划。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.