磁盘读取减慢 MySQL 中的 INSERT
Posted
技术标签:
【中文标题】磁盘读取减慢 MySQL 中的 INSERT【英文标题】:Disk-Read slows-down INSERT in MySQL 【发布时间】:2018-01-27 00:28:54 【问题描述】:我正在尝试优化我的 InnoDB 表上 MariaDB (10.0.31) 上的大型 INSERT 查询的速度。
这是表格的结构(1.31 亿行):
Field__ Type___ Null Key Default Extra
ID_num_ bigint(45) NO PRI NULL
Content varchar(250)YES NULL
User_ID bigint(24) NO MUL NULL
Location varchar(70) YES NULL
Date_creat datetime NO MUL NULL
Retweet_ct int(7) NO NULL
isRetweet tinyint(1) NO NULL
hasReetwet tinyint(1) NO NULL
Original bigint(45) YES NULL
Url____ varchar(150)YES NULL
Favorite_c int(7) NO NULL
Selected int(11) NO 0
Sentiment int(11) NO 0
这是CREATE TABLE
的输出:
CREATE TABLE `Twit` (
`ID_num` bigint(45) NOT NULL,
`Content` varchar(250) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`User_ID` bigint(24) NOT NULL,
`Location` varchar(70) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`Date_create` datetime NOT NULL,
`Retweet_count` int(7) NOT NULL,
`isRetweet` tinyint(1) NOT NULL,
`hasReetweet` tinyint(1) NOT NULL,
`Original` bigint(45) DEFAULT NULL,
`Url` varchar(150) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`Favorite_count` int(7) NOT NULL,
`Selected` int(11) NOT NULL DEFAULT '0',
`Sentiment` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`ID_num`),
KEY `User_ID` (`User_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
这是索引的结构:
Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
Twit 0 PRIMARY 1 ID_num A 124139401 NULL NULL BTREE
Twit 1 User_ID 1 User_ID A 535083 NULL NULL BTREE
这里是show engine innodb status
:
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 8942256128; in additional pool allocated 0
Total memory allocated by read views 184
Internal hash tables (constant factor + variable factor)
Adaptive hash index 141954688 (141606424 + 348264)
Page hash 4426024 (buffer pool 0 only)
Dictionary cache 35656039 (35403184 + 252855)
File system 845872 (812272 + 33600)
Lock system 21251648 (21250568 + 1080)
Recovery system 0 (0 + 0)
Dictionary memory allocated 252855
Buffer pool size 524286
Buffer pool size, bytes 8589901824
Free buffers 448720
Database pages 75545
Old database pages 27926
Modified db pages 0
Percent of dirty pages(LRU & free pages): 0.000
Max dirty pages percent: 75.000
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 74639, created 906, written 39133
0.12 reads/s, 0.00 creates/s, 0.00 writes/s
Buffer pool hit rate 999 / 1000, young-making rate 0 / 1000 not 0 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 75545, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
我使用以下 Python 代码从第 3 方源下载数据,然后用它填充我的表格:
add_twit = (" INSERT INTO Table (ID_num, Content,....) VALUES (%s, %s, ....)")
testtime=0
t0 = time.time()
data_twit = []
#### Data Retrieving ####
for page in limit_handled(...):
for status in page:
data_twit.append(processed_tweet)
####
##### mysql Insert
tt0 = time.time()
cursorSQL.executemany(add_twit, data_twit)
testtime += time.time() - tt0
####
cnx.commit()
print('Total_TIME ' + str(time.time()-t0))
print('Sqlexecute_TIME ' + str(testtime))
代码做了什么:
它从 3rd 方提供者那里获取 twits,其中 16 页,每页有 200 个 twits(状态),因此每个迭代(用户)总共要向表中添加 3200 行。我尝试在每条推文中插入一个查询(使用cursorSQL.execute(add_twit, data_twit)
,并且在列表中也包含 200 条推文的 16 个查询,但最快的几秒钟是使用优化的 cursorSQL.executemany
函数对 3200 条推文进行查询。
对于 3200 条推文,下载它们大约需要 10 秒,将它们写入数据库大约需要 75 秒,考虑到一条推文(行)当前在表中占用 0.2ko,这似乎很多,因此 3200 只有 640 Ko .不应该花 75 秒...
使用iotop
监控磁盘使用情况时会发生什么:
在大插入后,磁盘实际上会以 6Mbs/s 的速率持续写入几分钟
在代码的 SQL-Insert 部分:
读取 = 1.5 M/s 写入 = 300 K/s看起来磁盘读取(我猜是为了索引目的?)使写入速率下降。
我尝试了什么:
尝试拆分插入查询(而不是1*3200行我尝试了16*200行和3200*1行,没有改变任何东西,1*3200稍微快一点)
李>优化表(速度提高 15%)
删除不必要的索引
我的问题:
为什么当我提交 INSERT 查询而不是写入时磁盘开始读取?有没有办法防止这种情况发生?删除所有 INDEX 是否有助于加快 INSERT?
我是否需要删除主键(不是列,只是其上的唯一索引),即使这听起来是个坏主意,并且 (MySQL slows down after INSERT) 建议不要这样做?
还有其他建议吗? 另外,为什么磁盘在大插入数分钟后仍以 6.00 Mb/s 的速度写入?【问题讨论】:
网络延迟如何?您的数据库是否与前端进程位于同一台机器上? @joop ,不,不是,但它在同一个本地网络上。读取查询非常有效。我认为网络非常高效且延迟低。 嗯,在 请提供SHOW CREATE TABLE
请提供插入的SQL示例;并非所有人都知道从 Python 到 SQL 的映射。
【参考方案1】:
如果您有索引,那么您将通过磁盘读取来查找索引。当您插入以查找磁盘上的适当位置时,您将始终进行一些读取。
删除索引会加快插入速度,但代价是以后的读取操作。
是否删除主索引很大程度上取决于您的用例,以及您对数据源不存在完全重复的信任程度。但是,任何需要使用主键来读取数据库的东西都会在以后付出高昂的性能代价。但是,这将加快写入操作。
您可能需要考虑为您的 RDBMS 设置其他设置,例如分片,这将允许您分配负载。只有这么多问题可以在没有硬件扩展或至少某种并行性的情况下解决,而且可能不适合您的用例。
【讨论】:
谢谢,非常有趣的答案。只是对主键部分的一些说明。删除它会加快写作过程吗? 如果您使用 InnoDB,不要删除PRIMARY KEY
!
@RickJames YIKES。如果在多线程环境中进行访问,这是一个好点。【参考方案2】:
表中有大约 60GB?
User_ID 索引中大约有 5GB? (请参阅SHOW TABLE STATUS LIKE 'Twit
中的 Index_length。)
每个INSERT
有大约3200 个新行?如果这是错误的,那么这就是主要问题。
您正在计算 ID_num 而不是使用AUTO_INCREMENT
?
ID_num 单调递增? (或至少近似。)如果这是错误的,那么它就是主要问题。
User_ID 非常随机。
分析与结论:
数据正在“附加到”;这对缓存(buffer_pool,即 8GB)影响不大。User_ID
索引正在随机更新;这会将大部分索引保留在缓存中,或者可能会溢出。如果您刚刚开始溢出,那么性能正在下降,并且随着缓存未命中的增加,性能会越来越差。
“写入后 I/O 继续”- 这是正常的。有关详细信息,请查看“InnoDB 更改缓冲”。摘要:INDEX(User_ID)
的更新延迟,但最终必须发生。
部分解决方案:
更多内存。 将innodb_buffer_pool_size
增加到 RAM 的 70%;确保不要导致交换。
您的用户肯定不超过 40 亿吗?将User_ID
从BIGINT
(8 个字节)缩小到INT UNSIGNED
(4 个字节)。这将使二级索引缩小约 25%。
DROP INDEX(User_ID)
-- 你确定需要它吗?
您在其他地方使用ID_num
吗?如果不是,请解释它的存在。
在适当的情况下从 NULL
更改为 NOT NULL
。 (无助于加快速度,但可以进行清理。)
使用AUTO_INCREMENT
代替手动输入的ID。 (可能没有帮助。)
基准测试:
我不会使用任何“原始”I/O 指标——它们被 InnoDB 的“阻塞”和更改缓冲区弄糊涂了。 等待“稳定状态”。也就是避免小桌子、冷机、爆裂等。一张图每3200用了多长时间,都会因为这样的事情而起起落落。但最终它会达到一个“稳定状态”。但是,根据我对二级索引的分析,这可能会下降到需要 32 秒的 3200 行(如果使用旋转磁盘)。 75 秒内 3200 没有意义。我想我真的需要看看生成的 SQL。【讨论】:
非常感谢。惊人的答案。我还没有完全调查它,但首先只是一个快速的问题:100rows/sec(32 秒内 3200 行)是根据你的最大限制吗?在做了一些清理和增加 buffer_pool 之后,我现在接近 30/32 秒,我应该停止尝试更快吗?你是怎么得出这个数字的? 开箱即用,带有旋转驱动器(不是 SSD),InnoDB 将处理 100 次插入/秒。有多种技巧可以超过 100,也许是 10K。 1M 已经演示过了,但是很棘手。 100 是廉价驱动器上的 IOP; InnoDB 至少执行一次刷新到磁盘以保证事务完整性。 在单个语句中插入 100 行:10 倍加速。关闭某个标志会失去保证,但会消除 1 次冲洗。等等。TEXT
和 BLOB
由于非记录存储而存在问题。 (你没有这样的。)等等。
相关提示是:(1)尽可能批量插入; (2)考虑innodb_flush_log_at_trx_commit
的权衡。另请参阅我的blog 关于高速摄取;但这可能有点矫枉过正。以上是关于磁盘读取减慢 MySQL 中的 INSERT的主要内容,如果未能解决你的问题,请参考以下文章