mysql在具有1亿行的表上创建索引
Posted
技术标签:
【中文标题】mysql在具有1亿行的表上创建索引【英文标题】:mysql create index on table with a 100 million rows 【发布时间】:2013-08-10 21:16:51 【问题描述】:我的 mysql 表很少——这些表大约有 300 列和 1 亿行。这些存储日志文件的数据,因此大小。我正在使用 InnoDB 引擎。很少有涉及这些表连接的查询显然不起作用。我尝试为这些添加索引,但查询根本没有完成。
我想知道是否有任何其他方法可以提高性能,或者有什么方法可以让“创建索引”在表上工作?
谢谢。
【问题讨论】:
CREATE INDEX...
根本没有完成,或者您成功创建索引后的查询根本没有完成?你考虑过分区吗?
除了@Mike 所说的 - 对类似日志的表有索引是很常见的,因为它们是写密集型的,但在单独的“离线”服务器上进行分析
【参考方案1】:
创建索引需要时间,与表中的行数成正比。 1 亿行对于 MySQL 表来说是相当多的。在该表上创建索引可能需要 几个小时。具体多长时间会有所不同,具体取决于其他因素,包括您的服务器硬件、您为其创建索引的列的数据类型、数据库上的其他当前负载等。
一个可以帮助您的工具是pt-online-schema-change。构建索引实际上需要更长的时间,但是您可以在原始表工作的同时继续对其进行读写。使用较小的表进行测试,以便您获得使用此工具的一些经验。
您可以在此处查看有关此工具的网络研讨会:Zero-Downtime Schema Changes in MySQL(免费查看,但需要注册)。
另一种技术是像原始表一样创建一个空表,在该表中创建索引,然后开始将原始表中的数据逐渐复制到新表中。如果这是一个日志表,您对表的写入可能比从表中读取的多,因此您可能可以立即交换表并立即开始记录新事件,并随着时间的推移回填。
pt-archiver 之类的工具可以帮助您逐步复制数据,而不会给服务器带来过多负载。如果您尝试在一个事务中复制 1 亿行,那么简单地执行 INSERT INTO... SELECT
对您的数据库服务器的运行状况不利。它还会在原始表上加锁。 pt-archiver 通过一次只复制一小块行来工作,因此它避免了如此大的事务的高成本。
如果您使用自增主键,请注意将值调整为高于原始表中的最大值,然后再让日志事件开始写入它,这样您就不会意外地 id 值超过一次。
【讨论】:
【参考方案2】:使用
create table newtable like oldtable;
然后在新表为空时将索引应用于新表。
然后
insert into newtable select * from oldtable;
这也可能需要很长时间才能完成。
【讨论】:
您的解决方案非常有效。但是,对于我的任务来说,这并不实用。【参考方案3】:虫子
在使用 MyISAM 引擎的 MySQL 表上创建新二级索引时存在一些问题。
MyISAM 引擎的一个已知问题,在某些 MySQL 版本(例如 5.7.24(例如 Wamp 附带)上)不仅会按预期导致表扫描,而且在创建索引时需要重建全表。如果你只是删除一个索引,表也会被重建:-(
参考:https://bugs.mysql.com/bug.php?id=93530
另类
有时您无法升级 MySQL 或无法要求客户这样做,以运行您的解决方案。如果您不需要 InnoDB 提供的所有功能,将引擎更改为 InnoDB 可能会导致另一个问题。
索引表
因此,有一种方法包括手动创建“索引表”,其好处是您可以过滤您真正需要的记录,如下所述:
假设您在一张桌子上有 1 亿条世界公司的记录,其中大约 3000 万条是美国公司,1000 万条来自加拿大,以及其他公司。
每家公司都有一个 COUNTRY 和一个 STATE 字段,您要对其进行索引,因为您需要按其所在州搜索 USA 或 CANADA 公司。
因此,在 MySQL 中,如果您为 Country 和 State 创建索引,则所有 100M 记录都将被索引,即使是 NULL 状态。
要解决这个问题,您需要创建一个索引表和一个真实索引,如下所示:
create table index_tb_companies (
company_id int unique,
company_country char(2), -- US/CA
company_state char(2) -- AL/AK/.../WI/WY
);
create index index_tb_companies_index
on index_tb_companies (company_country, company_state);
填写索引表
现在您可以使用简单的insert into
或replace into
和过滤的select
将原始数据导入索引表。
replace into index_tb_companies(
company_id, company_country, company_state)
(select
company_id, company_country, company_state
from original_company_table
where country in ('US', 'CA')
);
这需要一段时间,因为您可能还没有国家/地区的索引,需要进行全表扫描。但最终的索引表大小将低于 MySQL 索引大小,因为只有 US/CA 数据会在其中。
如何选择
现在,最后一部分是使用索引表以及您对美国和加拿大公司的具体报告,因为其他国家/地区不包含在索引中。
select o.*
from
original_company_table o INNER JOIN
index_tb_companies idx ON idx.company_id = o.company_id
where
idx.company_country = 'US'
and idx.company_state = 'NY'
当您想在 MySQL 上索引一小部分数据时,这种方法特别好,因此索引大小很小。
部分索引
其他数据库,如 PostgreSQL,有一个“部分索引”,您可以创建常规索引并在创建时传递 where
子句。
PG 部分索引:https://www.postgresql.org/docs/8.0/indexes-partial.html
如果您从中学习到此解决方案,请点赞并分享,我正在制作一些有关数据库的材料并感谢您的反馈。
【讨论】:
以上是关于mysql在具有1亿行的表上创建索引的主要内容,如果未能解决你的问题,请参考以下文章