根据慢查询日志,以下查询(和类似查询)需要大约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
与此表相关的活动:
SELECT * FROM incoming_gprs_data WHERE completed = 0
获取行来处理这些行,处理它们并更新completed = 1
completed = 1
)以使表更加细长。SELECT
查询。我们之所以做#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
查询到表中的速度以及导致偶尔慢插入查询的原因。
答案 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)
INSERT
。 long_query_time
,默认值为10秒实际上是“无用的”。 不要一次扫描整个表格。走过桌子,最好是通过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)
最后,我们最终得到了一个不同的解决方案来解决这个问题,主要是因为我们插入的传入数据来自不同的来源(因此,不同的进程)。因此,我们不能在此问题中使用多行INSERT
和START TRANSACTION
以及COMMIT
。
要解决此问题,我们会将incoming_gprs_data
表更改为使用MEMORY
引擎。此表的作用类似于临时表,用于收集来自不同源的所有传入数据。然后我们将使用cron处理这些数据,将它们插入另一个表processed_data_xxx
,最后删除它们。这将删除所有慢插入查询。
我们理解拥有MEMORY
引擎的缺点(例如高波动性和缺乏排序和哈希索引)。但是,使用MEMORY
引擎进行书写和阅读的速度非常适合这种情况。
在将处理后的数据插入表格processed_data_xxx
时,我们已按照@Ollie Jones的建议使用START TRANSACTION
和COMMIT
,而不是自动提交每个插入查询。
答案 4 :(得分:0)
一个相当实用的解决方案是不进行直接插入,而是写入redis队列,然后每秒使用一次以进行批量插入。这些过程只需要几行代码(使用任何语言)。
类似于:在循环中读取队列中的所有记录并将它们插入到mysql中。在循环中睡眠x时间为100毫秒,直到挂钟再进一步,然后再次启动循环。
这是非常快速和实用的,但是您失去了成功插入数据库的实时确认。通过这种方法,我能够在一台机器上实现每秒高达40k的插入次数。