优化InnoDB插入查询

时间:2016-09-03 11:42:03

标签: mysql innodb database-performance bulkinsert bulk-load

根据慢查询日志,以下查询(和类似查询)需要大约2秒才能执行:

INSERT INTO incoming_gprs_data (data,type) VALUES ('3782379837891273|890128398120983891823881abcabc','GT100');

表格结构:

CREATE TABLE `incoming_gprs_data` (
 `id` int(200) NOT NULL AUTO_INCREMENT,
 `dt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `data` text NOT NULL,
 `type` char(10) NOT NULL,
 `test_udp_id` int(20) NOT NULL,
 `parse_result` text NOT NULL,
 `completed` tinyint(1) NOT NULL,
 PRIMARY KEY (`id`),
 KEY `completed` (`completed`)
) ENGINE=InnoDB AUTO_INCREMENT=5478246 DEFAULT CHARSET=latin1

与此表相关的活动:

  1. 每秒约有200行插入此表。传入的数据来自不同的来源(因此,它不会在一个进程中发生,而是在每秒进行多次处理)。
  2. cron流程将通过SELECT * FROM incoming_gprs_data WHERE completed = 0获取行来处理这些行,处理它们并更新completed = 1
  3. 另一个cron进程(每15分钟运行一次)将删除已完成的行(即completed = 1)以使表更加细长。
  4. 慢速日志查询不表示与该表有关的任何慢SELECT查询。
  5. 表的大小相对较小,不到200K行。
  6. 我们之所以做#2和#3,是因为之前我们发现删除已完成的行需要时间,因为索引需要重建。因此,我们添加了completed标志并且不太频繁地执行删除。这些更改有助于减少慢查询的数量。

    以下是我们拥有的innodb_settings:

    +---------------------------------+------------------------+
    | Variable_name                   | Value                  |
    +---------------------------------+------------------------+
    | have_innodb                     | YES                    |
    | ignore_builtin_innodb           | OFF                    |
    | innodb_adaptive_flushing        | ON                     |
    | innodb_adaptive_hash_index      | ON                     |
    | innodb_additional_mem_pool_size | 8388608                |
    | innodb_autoextend_increment     | 8                      |
    | innodb_autoinc_lock_mode        | 1                      |
    | innodb_buffer_pool_instances    | 2                      |
    | innodb_buffer_pool_size         | 6442450944             |
    | innodb_change_buffering         | all                    |
    | innodb_checksums                | ON                     |
    | innodb_commit_concurrency       | 0                      |
    | innodb_concurrency_tickets      | 500                    |
    | innodb_data_file_path           | ibdata1:10M:autoextend |
    | innodb_data_home_dir            |                        |
    | innodb_doublewrite              | OFF                    |
    | innodb_fast_shutdown            | 1                      |
    | innodb_file_format              | Antelope               |
    | innodb_file_format_check        | ON                     |
    | innodb_file_format_max          | Antelope               |
    | innodb_file_per_table           | ON                     |
    | innodb_flush_log_at_trx_commit  | 2                      |
    | innodb_flush_method             | O_DIRECT               |
    | innodb_force_load_corrupted     | OFF                    |
    | innodb_force_recovery           | 0                      |
    | innodb_io_capacity              | 200                    |
    | innodb_large_prefix             | OFF                    |
    | innodb_lock_wait_timeout        | 50                     |
    | innodb_locks_unsafe_for_binlog  | OFF                    |
    | innodb_log_buffer_size          | 67108864               |
    | innodb_log_file_size            | 536870912              |
    | innodb_log_files_in_group       | 2                      |
    | innodb_log_group_home_dir       | ./                     |
    | innodb_max_dirty_pages_pct      | 75                     |
    | innodb_max_purge_lag            | 0                      |
    | innodb_mirrored_log_groups      | 1                      |
    | innodb_old_blocks_pct           | 37                     |
    | innodb_old_blocks_time          | 0                      |
    | innodb_open_files               | 300                    |
    | innodb_purge_batch_size         | 20                     |
    | innodb_purge_threads            | 0                      |
    | innodb_random_read_ahead        | OFF                    |
    | innodb_read_ahead_threshold     | 56                     |
    | innodb_read_io_threads          | 4                      |
    | innodb_replication_delay        | 0                      |
    | innodb_rollback_on_timeout      | OFF                    |
    | innodb_rollback_segments        | 128                    |
    | innodb_spin_wait_delay          | 6                      |
    | innodb_stats_method             | nulls_equal            |
    | innodb_stats_on_metadata        | OFF                    |
    | innodb_stats_sample_pages       | 8                      |
    | innodb_strict_mode              | OFF                    |
    | innodb_support_xa               | ON                     |
    | innodb_sync_spin_loops          | 30                     |
    | innodb_table_locks              | ON                     |
    | innodb_thread_concurrency       | 0                      |
    | innodb_thread_sleep_delay       | 10000                  |
    | innodb_use_native_aio           | OFF                    |
    | innodb_use_sys_malloc           | ON                     |
    | innodb_version                  | 1.1.8                  |
    | innodb_write_io_threads         | 4                      |
    +---------------------------------+------------------------+
    

    在使用以下SQL查询进行计算后,我们已将innodb_buffer_pool_size设置为6G

    SELECT CEILING(Total_InnoDB_Bytes*1.6/POWER(1024,3)) RIBPS FROM (SELECT SUM(data_length+index_length) Total_InnoDB_Bytes FROM information_schema.tables WHERE engine='InnoDB') A;
    

    它会生成5GB的结果。我们估计它的InnoDB表格不会超过这个大小。

    目前我们主要关注的是如何加快insert查询到表中的速度以及导致偶尔慢插入查询的原因。

5 个答案:

答案 0 :(得分:4)

如您所知,插入每秒200行很多。尝试在这种规模的应用程序上优化此数据流是值得的。

InnoDB使用database transactions on all insertions。也就是说,每个插入都是这样的:

 START TRANSACTION;
 INSERT something...;
 COMMIT;

如果您未指定这些事务,则会获得自动提交行为。

以高容量进行插入的秘诀是在每个事务中执行许多操作,如下所示:

 START TRANSACTION;
 INSERT something...;
 INSERT something...;
 ...
 INSERT something...;
 INSERT something...;
 INSERT something...;
 COMMIT;
 START TRANSACTION;
 INSERT something...;
 INSERT something...;
 ...
 INSERT something...;
 INSERT something...;
 INSERT something...;
 COMMIT;
 START TRANSACTION;
 INSERT something...;
 INSERT something...;
 ...
 INSERT something...;
 INSERT something...;
 INSERT something...;
 COMMIT;

在每个INSERT

之前,我已经取得了很好的成功,最多有一百个COMMIT;命令

不要忘记最后的COMMIT不要问我怎么知道提出这个建议。 : - )

在MySQL中执行此操作的另一种方法是使用多行INSERT命令在您的情况下,它们可能看起来像这样。

INSERT INTO incoming_gprs_data (data,type) VALUES
    ('3782379837891273|890128398120983891823881abcabc','GT100'),
    ('3782379837891273|890128398120983891823881abcabd','GT101'),
    ('3782379837891273|890128398120983891823881abcabe','GT102'),
       ...
    ('3782379837891273|890128398120983891823881abcabf','GT103'),
    ('3782379837891273|890128398120983891823881abcac0','GT104');

第三种方法,即获得非常高的插入率,最难和最高性能的方法是将批量数据存储在文本文件中,然后使用the LOAD DATA INFILE command将数据放入表中。这种技术确实非常快,特别是如果文件可以直接从MySQL服务器的文件系统加载。

我建议您首先尝试交易,看看您是否获得了所需的性能。

另一件事:如果你有一个安静的白天或黑夜时间,你可以删除已完成的行,而不是每十五分钟。在任何情况下,当您回读这些要处理或删除的行时,您应该使用这样的事务处理批处理:

   done = false   /* pseudocode for your programming language */
   while not done {
       DELETE FROM table WHERE completed = 1 LIMIT 50;
       if that query handled zero rows {
           done = true
       }
   }

这将在合理大小的事务批次中执行删除操作。您偶尔会有两秒钟的插入延迟,这可能是因为您处理或删除了一个非常大的事务批处理。

答案 1 :(得分:1)

  • 每秒约有200行插入此表。 一次一个?更好的是做一个多行INSERT
  • 慢速日志查询并不表示与表有关的任何慢查询。 降低long_query_time,默认值为10秒实际上是“无用的”。
  • Cron进程执行SELECT * FROM incoming_gprs_data WHERE completed = 0。

不要一次扫描整个表格。走过桌子,最好是通过PRIMARY KEY,一次做1000行。 More details on chunking

索引“重建”,它总是逐步更新。 (我希望你没有明确地重建它!)

我假设你有至少8GB的内存? (buffer_pool是我的线索,应该是可用内存的70%左右。)

int(200) - (200)没有任何意义。无论INT是4个字节。

不要做两个cron进程;继续并在第一次通过时删除。设置UPDATE的{​​{1}}费用与completed相同。

更多

如果你不能“批量”插入,你至少可以在一个“交易”中使用它们(DELETE ... BEGIN)吗?同上COMMIT。对于数据完整性,每个事务至少有一个磁盘命中。因此,在单个事务中执行多个操作会减少I / O,从而加快查询速度。但是......不要被带走;如果您在单个事务中执行了一百万次插入/删除/更新,则还有其他问题。

可以采取另一项措施来减少I / O开销:DELETEs,它比innodb_flush_log_at_trx_commit = 2的默认值更快但安全性更低。如果您的“200次插入/秒”是200次交易(例如使用1),则此设置更改可能会产生很大的不同。

答案 2 :(得分:1)

你发表了自己的答案:

  

要解决此问题,我们会更改incoming_gprs_data表以使用MEMORY   发动机。这个表就像一个临时表来收集所有的   来自不同来源的传入数据。然后我们将使用cron will   处理这些数据,将它们插入另一个表processed_data_xxx,   最后删除它们。这将删除所有慢速插入查询。

您应该为此使用消息队列,而不是数据库。如果您有一个处理数据然后将其删除的工作流,这对于消息队列来说听起来很完美。有许多消息队列可以轻松地每秒处理200个条目。

您可以让应用程序监听消息队列中的主题,处理项目,然后......没有任何内容,而不是从数据库更新和删除记录的cron作业。无需存储该项目,只需转到队列中的下一个项目即可。

我们在现有公司使用Apache ActiveMQ。我也知道推荐RabbitMQ的其他开发者。

答案 3 :(得分:0)

最后,我们最终得到了一个不同的解决方案来解决这个问题,主要是因为我们插入的传入数据来自不同的来源(因此,不同的进程)。因此,我们不能在此问题中使用多行INSERTSTART TRANSACTION以及COMMIT

要解决此问题,我们会将incoming_gprs_data表更改为使用MEMORY引擎。此表的作用类似于临时表,用于收集来自不同源的所有传入数据。然后我们将使用cron处理这些数据,将它们插入另一个表processed_data_xxx,最后删除它们。这将删除所有慢插入查询。

我们理解拥有MEMORY引擎的缺点(例如高波动性和缺乏排序和哈希索引)。但是,使用MEMORY引擎进行书写和阅读的速度非常适合这种情况。

在将处理后的数据插入表格processed_data_xxx时,我们已按照@Ollie Jones的建议使用START TRANSACTIONCOMMIT,而不是自动提交每个插入查询。

答案 4 :(得分:0)

一个相当实用的解决方案是不进行直接插入,而是写入redis队列,然后每秒使用一次以进行批量插入。这些过程只需要几行代码(使用任何语言)。

类似于:在循环中读取队列中的所有记录并将它们插入到mysql中。在循环中睡眠x时间为100毫秒,直到挂钟再进一步,然后再次启动循环。

这是非常快速和实用的,但是您失去了成功插入数据库的实时确认。通过这种方法,我能够在一台机器上实现每秒高达40k的插入次数。