如何优化MySQL中的大表,何时可以从分区中受益?

时间:2016-11-10 04:51:08

标签: mysql indexing database-performance partitioning

总之,日期范围分区和内存配置实现了我的目标。

我需要增加分配给 innodb_buffer_pool_size 的内存,因为默认的8M太低了。 Rick James建议70% of RAM这个设置,他有很多很好的信息。

Edlerd的建议是正确的: - )

我将数据拆分为月分区,然后运行6,000行响应查询,最初需要6到12秒。它现在在不到一秒钟内完成(.984 / .031)。我使用默认的innodb缓冲区大小(innodb_buffer_pool_size = 8M)来运行它,以确保它不仅仅是内存增加。

然后我设置innodb_buffer_pool_size = 4G并以更好的响应运行查询.062 / .032。

我还想提一下,增加内存也提高了我的Web应用程序和服务的整体速度,它接收和写入消息到这个表,我很惊讶这个配置设置有多大区别。我的网络服务器的第一个字节时间(TTFB)现在几乎与MySQL Workbench相当,有时会达到20秒。

我还发现slow query log file是识别问题的一个很好的工具,我在那里看到它表明我的innodb_buffer_pool_size很低并且突出了所有表现不佳的查询。这也确定了我需要索引其他表的区域。

编辑2016-11-12解决方案

我正在重构一个记录遥测数据的大型表,它已经运行了大约4-5个月并且已经产生了大约。平均行数约为5400万条记录。 380字节。

我已经开始在我的一个原始数据查询中看到一些性能延迟,这些查询会在24小时内返回设备的所有日志。

最初我认为它是索引,但我认为这是MySQL需要处理的I / O量。一个典型的24小时查询将包含 2.2k 3k到9k记录,我实际上想支持大约7天的导出。

我对数据库性能调优没有经验,所以仍然只是学习绳索。我正在考虑一些策略。

  1. 根据查询原始数据调整复合索引,虽然我认为我的索引没问题,因为解释计划显示100%命中率。
  2. 考虑创建覆盖索引以包含所需的所有行
  3. 按日期实施远程分区: a)保留每月分区。例如。过去6个月 b)将较旧的内容移至归档表格。
  4. 使用原始数据创建单独的表(垂直分区),并将其与主查询表的ID连接。我的索引正在运行,不确定这是我的问题。
  5. 更改我的查询以批量提取数据,然后按创建的日期限制X排序并继续,直到不再返回任何记录。
  6. 查看服务器配置
  7. 1,2(INDEXES): 我会用我的查询重写我的索引,但我认为我很好,因为Explain显示100%命中,除非我读错了。

    我会在重建时尝试覆盖索引,但是如何确定设置错误的效果呢?例如。插入速度受到影响。

    如何在实时环境中最好地监控桌面的性能?

    编辑:我刚刚开始使用slow log file,它看起来像是查找问题的好工具,我认为performance_schema上的查询可能是另一个问题选项?

    3(PARTITIONING): 我已经阅读了一些关于分区的内容,并且不确定我的数据大小是否会产生很大影响。

    Rick James suggests> 1M记录,我是54M,并希望在归档之前保留大约300M,我的桌子是否足够复杂?

    我必须自己测试一下,因为我没有任何这方面的经验,这对我来说都是理论上的。如果它不适合我的需要,我只是不想走这条路。

    4(通过'加入'细节表进行垂直分区):我不认为我有表扫描问题而且我需要所有行,所以我不确定这种技术会有什么好处

    5(使用限制并再次获取):如果我在单个请求中使用较少的时间,是否可以释放服务器?我是否会在同一连接上以更多命令为代价看到更好的I / O吞吐量?

    6(评论配置):另一部分是审核安装MySQL时使用的默认非开发人员配置,也许有一些设置可以调整? : - )

    感谢阅读,热衷于听取任何和所有建议。

    以下FYI:

    表格

    CREATE TABLE `message_log` (
        `db_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
        `db_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `created` datetime DEFAULT NULL,
        `device_id` int(10) unsigned NOT NULL,
        `display_name` varchar(50) DEFAULT NULL,
        `ignition` binary(1) DEFAULT NULL COMMENT 'This is actually IO8 from the falcom device',
        `sensor_a` float DEFAULT NULL,
        `sensor_b` float DEFAULT NULL,
        `lat` double DEFAULT NULL COMMENT 'default GPRMC format ddmm.mmmm \n',
        `lon` double DEFAULT NULL COMMENT 'default GPRMC longitude format dddmm.mmmm ',
        `heading` float DEFAULT NULL,
        `speed` float DEFAULT NULL,
        `pos_validity` char(1) DEFAULT NULL,
        `device_temp` float DEFAULT NULL,
        `device_volts` float DEFAULT NULL,
        `satellites` smallint(6) DEFAULT NULL, /* TINYINT will suffice */
        `navdist` double DEFAULT NULL,
        `navdist2` double DEFAULT NULL,
        `IO0` binary(1) DEFAULT NULL COMMENT 'Duress',
        `IO1` binary(1) DEFAULT NULL COMMENT 'Fridge On/Off',
        `IO2` binary(1) DEFAULT NULL COMMENT 'Not mapped',
        `msg_name` varchar(20) DEFAULT NULL, /* Will be removed */
        `msg_type` varchar(16) DEFAULT NULL, /* Will be removed */
        `msg_id` smallint(6) DEFAULT NULL,
        `raw` text, /* Not needed in primary query, considering adding to single table mapped to this ID or a UUID correlation ID to save on @ROWID query */
    PRIMARY KEY (`db_id`),
    KEY `Name` (`display_name`),
    KEY `Created` (`created`),
    KEY `DeviceID_AND_Created` (`device_id`,`created`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    DeviceID_AND_Created是主要索引。我需要PK聚簇索引,因为我在摘要表中使用记录ID来跟踪给定设备的最后一条消息。创建的将是分区列,所以我想这也会被添加到PK集群中?

    QUERY:

    SELECT 
        ml.db_id, ml.db_created, ml.created, ml.device_id, ml.display_name, bin(ml.ignition) as `ignition`, 
        bin(ml.IO0) as `duress`, bin(ml.IO1) as `fridge`,ml.sensor_a, ml.sensor_b, ml.lat, ml.lon, ml.heading, 
        ml.speed,ml.pos_validity, ml.satellites, ml.navdist2, ml.navdist,ml.device_temp, ml.device_volts,ml.msg_id
    FROM message_log ml 
    WHERE ml.device_id = @IMEI
    AND ml.created BETWEEN @STARTDATE AND DATE_ADD(@STARTDATE,INTERVAL 24 hour) 
    ORDER BY ml.db_id;
    

    这将返回给定24小时内的所有日志,此时此时间约为。 3k到9k行,平均行大小为381个字节,一旦我删除其中一个TEXT字段(原始)将减少

3 个答案:

答案 0 :(得分:2)

  

按日期实施远程分区:a)保留每月分区。例如。最近6个月b)将旧的东西移到归档表。

这是非常好主意。我猜所有的写入都将在最新的分区中,您将只查询最近的数据。您总是希望数据和索引适合内存。因此读取时没有磁盘i / o。

根据您的使用情况,每周一个分区甚至可能是明智之举。然后,您只需要在内存中保留最多两周的数据,以便在过去7天内阅读。

如果您在使用myisam引擎时使用innodb作为引擎或myisam_key_cache,您可能还想调整缓冲区大小(即innodb_buffer_pool_size)。

同时将ram添加到数据库计算机通常会有所帮助,因为操作系统可以将数据文件存储在内存中。

如果您有大量写入,您还可以调整其他选项(即使用innodb_log_buffer_size将写入持久保存到磁盘的频率)。这是为了让脏页在内存中的时间更长,以避免过于频繁地将它们写回磁盘。

答案 1 :(得分:1)

对于那些好奇的人,以下是我用来创建分区和配置内存的内容。

创建分区

  1. 更新了PK以包含分区

    中使用的范围列
    ALTER TABLE message_log 
    CHANGE COLUMN created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    DROP PRIMARY KEY,
    ADD PRIMARY KEY (db_id, created);
    
  2. 使用ALTER TABLE添加分区。

  3. 事后看来,我应该将每个分区创建为单个ALTER语句,并在后续分区上使用Reorganize Partition(和here),因为在一次命中中执行它会占用大量资源和时间。 / p>

    ALTER TABLE message_log 
    PARTITION BY RANGE(to_days(created)) (
        partition invalid VALUES LESS THAN (0),
        partition from201607 VALUES LESS THAN (to_days('2016-08-01')),
        partition from201608 VALUES LESS THAN (to_days('2016-09-01')),
        partition from201609 VALUES LESS THAN (to_days('2016-10-01')),
        partition from201610 VALUES LESS THAN (to_days('2016-11-01')),
        partition from201611 VALUES LESS THAN (to_days('2016-12-01')),
        partition from201612 VALUES LESS THAN (to_days('2017-01-01')),
        partition from201701 VALUES LESS THAN (to_days('2017-02-01')),
        partition from201702 VALUES LESS THAN (to_days('2017-03-01')),
        partition from201703 VALUES LESS THAN (to_days('2017-04-01')),
        partition from201704 VALUES LESS THAN (to_days('2017-05-01')),
        partition future values less than (MAXVALUE) 
    );
    

    注意:我不确定使用to_days()或原始列是否会产生很大的不同,但我已经看到它在大多数示例中使用过,所以我把它作为假设最好的实践。

    设置缓冲池大小

    要更改 innodb_db_buffer_pool_size 的值,您可以找到以下信息: MySQL InnoDB Buffer Pool ResizeRick Jame's page on memory

    您也可以在 选项文件 菜单中的MySQL Workbench中执行此操作,然后使用innoDB选项卡。您在此处所做的任何更改都将写入配置文件中,但您需要停止并启动MySQL以读取配置,否则您还可以设置全局值以进行实时更新。

答案 2 :(得分:1)

这样的交易!即使没有写评论或回答,我也会得到4次提及。我正在写一个答案,因为我可能会有一些进一步的改进......

是的,PARTITION BY RANGE(TO_DAYS(...))是正确的方法。 (可能有个替代品。)

4GB内存中有70%是紧张的。确保没有交换。

您提到了一个查询。如果它是主要的问题,那么这会稍微好一些:

PRIMARY KEY(device_id, created, db_id),  -- desired rows will be clustered
INDEX(db_id)  -- to keep AUTO_INCREMENT happy

如果您没有清除旧数据,那么即使没有分区,上述关键建议也能提供相同的效率。

lat/lon representationDOUBLE有点矫枉过正。

小心inefficiency of UUID,特别是对于大桌子。