python - 磁盘读取会减慢 MySQL 中的 INSERT 速度

标签 python mysql indexing mariadb sql-insert

我正在尝试优化 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       

这是显示引擎innodb状态:

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))

代码的作用:

它从第 3 方提供商处获取 twits,共 16 页,每页 200 个 twits(状态),因此每个迭代(用户)总共需要将 3200 行添加到表中。我尝试对每条推文插入一个查询(使用 cursorSQL.execute(add_twit, data_twit)),并在列表中对 200 条推文进行 16 次查询,但最快的几秒是对 3200 条推文进行一次查询使用优化的 cursorSQL.executemany 函数发送推文。

对于 3200 条推文,下载它们大约需要 10 秒,将它们写入数据库大约需要 75 秒,考虑到表中一条推文(行)当前需要 0.2ko,因此这似乎很多,因此 3200 条只有 640 Ko 。不应该花 75 秒...

使用iotop监控磁盘使用情况时会发生什么:

  • 在代码的数据检索部分(第一次迭代之后):
    • 读取 = 0.00 B/s
    • 写入= 6.50 M/s

在一次大插入后,磁盘实际上会以 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 )建议不要这样做?

  • 还有其他建议吗?
  • 另外,为什么在一次大的 INSERT 之后,磁盘仍以 6.00 Mb/s 的速度写入?

最佳答案

  • 表中大约有 60GB?
  • User_ID 索引大约有 5GB? (请参阅 SHOW TABLE STATUS LIKE 'Twit 中的 Index_length。)
  • 每个 INSERT 大约有 3200 个新行?如果这是错误的,那么这就是主要问题。
  • 您正在计算 ID_num 而不是使用 AUTO_INCRMENT
  • ID_num 是单调递增的吗? (或者至少大约如此。)如果这是错误的,那么这就是主要问题。
  • User_ID 非常随机。

分析与结论:

  • 数据正在“附加到”;这对缓存(buffer_pool,8GB)没有太大影响。
  • User_ID 索引正在随机更新;这会将大部分索引保留在缓存中,或者可能会溢出。如果你现在才开始溢出,那么性能就会下降,并且随着缓存未命中的增加,性能会变得越来越差。
  • “写入后 I/O 继续”——这是正常现象。有关详细信息,请查找“InnoDB Change buffering”。摘要:INDEX(User_ID) 的更新会延迟,但最终一定会发生。

部分解决方案:

  • 更多内存。
  • innodb_buffer_pool_size 增加到 RAM 的 70%;确保不要导致交换。
  • 您的用户肯定没有超过 40 亿吧?将 User_IDBIGINT(8 字节)缩小为 INT UNSIGNED(4 字节)。这将使二级索引缩小约 25%。
  • DROP INDEX(User_ID) -- 您确实需要它吗?
  • 您在其他地方使用过 ID_num 吗?如果没有,请解释它的存在。
  • 在适当的情况下从 NULL 更改为 NOT NULL。 (不会提高速度,但可以进行清理。)
  • 使用AUTO_INCRMENT而不是手动滚动的ID。 (可能没有帮助。)

基准测试:

  • 我不会使用任何“原始”I/O 指标——它们会被 InnoDB 和 Change buffer 的“阻塞”所混淆。
  • 等待“稳定状态”。就是避免小表、冷机、爆裂等。一张3200花多长时间的图表就会因为这样的事情而出现起伏。但最终它会达到“稳定状态”。但是,根据我对二级索引的分析,可能会下降到 3200 行,需要 32 秒(如果使用旋转磁盘)。
  • 75 秒内 3200 没有意义。我想我确实需要查看生成的 SQL。

关于python - 磁盘读取会减慢 MySQL 中的 INSERT 速度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45754613/

相关文章:

python - pytorch 中的 tf.cast 等价物?

git - 如何转储 MySQL 数据库的结构并将其导入以使用现有数据更改表?

sql - mysql语法解释

c - 被标记化的 fgets() 字符串是否能够被索引到字符中? :line[0], 错误:段

mysql - 数据库索引中存储的值是什么?

python - 将图像数据转换为 3D

python - 在 Python 中使用自定义函数解析方程式

python - 正则表达式点不起作用

mysql - Node.js/Express - 在哪里放置 MySQL 连接?

python - 交错 2 个长度不等的列表