磁盘读取减慢了MySQL中的INSERT

时间:2017-08-18 10:36:44

标签: python mysql indexing mariadb sql-insert

我正在尝试在InnoDB表上优化MariaDB(10.0.31)上的大型INSERT查询的速度。

这是表格(131百万行)的结构:

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代码从第三方源下载数据,然后用它填充我的表:

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

代码的作用是什么:

它从第三方提供商那里获得两次,其中有16页,每页有200个twits(状态),因此每个iteratin(用户)共有3200行要添加到表中。我尝试使用每个推文一个查询插入(cursorSQL.execute(add_twit, data_twit),并在列表中插入200个推文的16个查询,但最快几秒钟使用优化的cursorSQL.executemany函数进行一次3200条推文查询

对于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)建议不要? / p>

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

2 个答案:

答案 0 :(得分: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 Change buffering”。摘要:INDEX(User_ID)的更新被延迟,但最终必须发生。

部分解决方案:

  • 更多内存。
  • innodb_buffer_pool_size增加到内存的70%;一定不要导致交换。
  • 肯定你的用户不超过40亿?从User_ID(8字节)收缩INT UNSIGNEDBIGINT(4个字节)。这将使二级指数缩小约25%。
  • DROP INDEX(User_ID) - 当然你需要它吗?
  • 你在其他地方使用ID_num吗?如果没有,请解释它的存在。
  • 在适当的情况下从NULL更改为NOT NULL。 (无助于速度,但需要清理。)
  • 使用AUTO_INCREMENT代替手动ID。 (可能没有帮助。)

基准:

  • 我不会使用任何“原始”I / O指标 - 它们会被InnoDB和更改缓冲区的“阻塞”所困惑。
  • 等待“稳定状态”。也就是说,避免小桌子,冷机器,爆裂等。每个3200花费多长时间的图表会因此而起伏不定。但最终它将达到“稳定状态”。但是,根据我对二级索引的分析,可能会下降到3200行需要32秒(如果使用旋转磁盘)。
  • 3200在75秒内没有意义。我想我真的需要看看生成的SQL。

答案 1 :(得分:0)

如果你有索引,那么你将有磁盘读取来搜索索引。当您插入以在磁盘上找到适当的位置时,您将始终进行一些读取。

删除索引会加快插入速度,但以后会以读取操作为代价。

是否删除主索引在很大程度上取决于您的使用案例,您信任数据源的多少不具备完全重复。但是,任何需要使用主键读取数据库的内容都将在以后大幅降低性能。但是,这将加快写入操作。

您可能需要考虑RDBMS的其他设置,例如分片,这样可以分配负载。只有很多问题可以在没有硬件扩展或至少某种并行性的情况下解决,并且可能不适合您的用例。