2012-03-22 75 views
43

我使用大型隨機數字作爲關鍵字(從另一個系統進入)。插入和更新相當小的(如幾百萬行)表所花費的時間比我認爲合理的要長得多。爲什麼MySQL InnoDB插入如此緩慢?

我已經蒸餾非常簡單的測試來說明。在測試表中,我試圖儘可能簡化它;我真正的代碼沒有這樣簡單的佈局,並且有關係和額外的索引等等。但是,更簡單的設置會顯示相同的性能。

下面是結果:

creating the MyISAM table took 0.000 seconds 
creating 1024000 rows of test data took 1.243 seconds 
inserting the test data took 6.335 seconds 
selecting 1023742 rows of test data took 1.435 seconds 
fetching 1023742 batches of test data took 0.037 seconds 
dropping the table took 0.089 seconds 
creating the InnoDB table took 0.276 seconds 
creating 1024000 rows of test data took 1.165 seconds 
inserting the test data took 3433.268 seconds 
selecting 1023748 rows of test data took 4.220 seconds 
fetching 1023748 batches of test data took 0.037 seconds 
dropping the table took 0.288 seconds 

插入1M行插入的MyISAM花費6秒;進入InnoDB需要3433秒

我在做什麼錯?什麼是錯誤配置? (MySQL是一個正常的Ubuntu安裝使用默認值)

下面是測試代碼:

import sys, time, random 
import MySQLdb as db 

# usage: python script db_username db_password database_name 

db = db.connect(host="127.0.0.1",port=3306,user=sys.argv[1],passwd=sys.argv[2],db=sys.argv[3]).cursor() 

def test(engine): 

    start = time.time() # fine for this purpose 
    db.execute(""" 
CREATE TEMPORARY TABLE Testing123 (
k INTEGER PRIMARY KEY NOT NULL, 
v VARCHAR(255) NOT NULL 
) ENGINE=%s;"""%engine) 
    duration = time.time()-start 
    print "creating the %s table took %0.3f seconds"%(engine,duration) 

    start = time.time() 
    # 1 million rows in 100 chunks of 10K 
    data = [[(str(random.getrandbits(48)) if a&1 else int(random.getrandbits(31))) for a in xrange(10*1024*2)] for b in xrange(100)] 
    duration = time.time()-start 
    print "creating %d rows of test data took %0.3f seconds"%(sum(len(rows)/2 for rows in data),duration) 

    sql = "REPLACE INTO Testing123 (k,v) VALUES %s;"%("(%s,%s),"*(10*1024))[:-1] 
    start = time.time() 
    for rows in data: 
     db.execute(sql,rows) 
    duration = time.time()-start 
    print "inserting the test data took %0.3f seconds"%duration 

    # execute the query 
    start = time.time() 
    query = db.execute("SELECT k,v FROM Testing123;") 
    duration = time.time()-start 
    print "selecting %d rows of test data took %0.3f seconds"%(query,duration) 

    # get the rows in chunks of 10K 
    rows = 0 
    start = time.time() 
    while query: 
     batch = min(query,10*1024) 
     query -= batch 
     rows += len(db.fetchmany(batch)) 
    duration = time.time()-start 
    print "fetching %d batches of test data took %0.3f seconds"%(rows,duration) 

    # drop the table 
    start = time.time() 
    db.execute("DROP TABLE Testing123;") 
    duration = time.time()-start 
    print "dropping the table took %0.3f seconds"%duration 


test("MyISAM") 
test("InnoDB") 

回答

33

的InnoDB不與 '隨機' 主鍵以及應對後

START TRANSACTION 

這:

你的循環之前執行此命令。嘗試一個順序鍵或自動增量,我相信你會看到更好的表現。你的'真正的'關鍵字段仍然可以編入索引,但是對於批量插入,你可能最好在插入完成後刪除並重新創建索引。會有興趣看到你的基準!

一些相關的問題

+2

>有興趣看到你的基準! MyISAM:使用自動遞增鍵創建一個表,然後向隨機鍵字段添加一個索引,這與使用之前索引的隨機字段創建表大致一樣快;全部在8秒內。 InnoDB:使用自動增量主鍵插入需要54秒。然後在該隨機字段上創建索引需要214秒。慢,但*大量*比使用隨機密鑰插入更快。 – Will 2012-04-05 13:21:49

+0

Paul,關於順序鍵的性能和優點的一般問題:只要鍵仍然存在,按鍵中是否存在間隙,是否重要?即:1,5,10,500,1234,7800等。我已閱讀了許多有關按鍵順序好處的資料,但我不確定「順序」是否意味着按升序排列(可能存在空白),或者如果順序意味着沒有差距。好奇,因爲這與我正在使用的多服務器密鑰生成系統有關,我在StackOverflow問題#6338956中討論了這個問題。謝謝。 – YeB 2013-02-01 01:50:32

+6

隨機密鑰插入如此之慢的原因是InnoDB以主鍵順序存儲行,而不是擁有巨大的具有單獨主鍵索引的行數據隨機池。這意味着如果您插入(僅)id = 1的記錄和另一個id = 10的記錄,兩行的數據將並排存儲。如果你插入一個id = 5的記錄,InnoDB必須移動id = 10的數據,以便將整個id = 5記錄放入表中。做很多次,你會發現很多數據被移動了很多次。對於隨機密鑰,你無能爲力。 – 2013-04-06 16:52:06

54

InnoDB的具有事務支持,你沒有使用明確的事務,以便InnoDB中必須做每個語句後提交("performs a log flush to disk for every insert" )。你循環

COMMIT 
+3

我加了這個,它仍在運行......我想我會在3000秒左右回覆你,並告訴你它沒有什麼不同......;) – Will 2012-03-22 09:29:55

+3

它仍然在運行,所以不,這不是問題 – Will 2012-03-22 10:20:19

+2

這救了我。我不得不插入大約900萬行 - 在24小時後,它只完成了10%。我修改它爲一個交易根據您的文章,並在大約2小時內完成! – 2013-02-15 17:09:17

4

InnoDB的默認值實際上是非常糟糕的。 InnoDB非常依賴內存,如果你調整了設置,你可能會發現更好的結果。下面是我用InnoDB optimization basic

19

我需要做兩MyISAM和InnoDB同時插入重型應用的測試指南。有一個解決我遇到的速度問題的設置。嘗試設置以下內容:

innodb_flush_log_at_trx_commit = 2 

請確保您通過閱讀關於設置here瞭解風險。

另見https://dba.stackexchange.com/questions/12611/is-it-safe-to-use-innodb-flush-log-at-trx-commit-2/12612https://dba.stackexchange.com/a/29974/9405

+0

這節省了我的培根,謝謝。我在一個嚴密的循環中遇到了非常差的性能,在這個循環中我必須知道每個新創建的行的ID,因此無法進行批量插入。我理解這個問題可能會丟失一秒鐘的數據,但在這個特定的服務器上是可以接受的。 – 2014-09-26 17:54:37

5

我得到我的系統上非常不同的結果,但這不是使用默認值。你可能會受到innodb-log-file-size的限制,默認爲5M。在InnoDB的日誌文件大小= 100M我得到的結果如下(所有數字單位爲秒):

       MyISAM  InnoDB 
create table     0.001  0.276 
create 1024000 rows   2.441  2.228 
insert test data    13.717  21.577 
select 1023751 rows   2.958  2.394 
fetch 1023751 batches   0.043  0.038 
drop table     0.132  0.305 

增加innodb-log-file-size將由幾秒鐘加速此。通過設置innodb-flush-log-at-trx-commit=20來降低耐用性保證,也會稍微改善插入數量。

2

什麼是你的innodb緩衝池大小?確保您已將其設置爲RAM的75%。 InnoDB主鍵順序通常插入效果更好。但是,如果游泳池很大,你應該看到很好的速度。

1

的事情,加快插入:

  • 我已經刪除表中的所有鍵之前大INSERT INTO空表
  • 然後發現我已經是該指數不適合在內存中的一個問題。
  • 也發現我有sync_binlog = 0(應該是1),即使binlog不使用。
  • 也發現我沒有設置innodb_buffer_pool_instances
-1

的MySQL 5.7:我做了一個測試,現在和我沒有注意到隨機主鍵和自動遞增的一個之間的顯着差異。

2

這是一個古老的話題,但經常搜索。只要你知道在最後一秒鐘左右,之前大規模更新,您可以設置這些全局參數丟失提交的事務風險(用如上述@philip Koshy說明)的

innodb-flush-log-at-trx-commit=0 
sync_binlog=0 

然後打開然後再返回開啓(如果需要)更新完成後。

innodb-flush-log-at-trx-commit=1 
sync_binlog=1 

符合完整的ACID。

當這些關閉和打開時,寫入/更新性能存在巨大差異。根據我的經驗,上面討論的其他材料有一些差異,但只有邊緣。

大大影響update/insert的另一件事是全文索引。在一種情況下,具有全文索引的兩個文本字段的表格插入2mil行需要6小時,並且在全文索引被移除後僅需要10分鐘。更多的索引,更多的時間。因此,在大量插入/更新之前,除了唯一鍵和主鍵之外的搜索索引可能會被刪除。