Làm thế nào MyISAM có thể "nhanh" hơn InnoDB nếu
- MyISAM cần làm đĩa đọc dữ liệu?
- InnoDB sử dụng nhóm bộ đệm cho các chỉ mục và dữ liệu và MyISAM chỉ dành cho chỉ mục?
Làm thế nào MyISAM có thể "nhanh" hơn InnoDB nếu
Câu trả lời:
Cách duy nhất MyISAM có thể nhanh hơn là InnoDB sẽ ở trong hoàn cảnh độc đáo này
Khi đọc, chỉ mục của bảng MyISAM có thể được đọc một lần từ tệp .MYI và được tải trong Bộ nhớ cache khóa MyISAM (có kích thước theo key_buffer_size ). Làm thế nào bạn có thể làm cho bảng MyISAM .MYD nhanh hơn để đọc? Với cái này:
ALTER TABLE mytable ROW_FORMAT=Fixed;
Tôi đã viết về điều này trong các bài viết trước đây của tôi
OK, còn InnoDB thì sao? InnoDB có thực hiện bất kỳ I / O đĩa nào cho các truy vấn không? Đáng ngạc nhiên, đúng vậy !! Có lẽ bạn đang nghĩ tôi điên khi nói điều đó, nhưng nó hoàn toàn đúng, ngay cả đối với các truy vấn CHỌN . Tại thời điểm này, có lẽ bạn đang tự hỏi "Làm thế nào trên thế giới InnoDB thực hiện I / O đĩa cho các truy vấn?"
Tất cả quay trở lại với InnoDB là một Công cụ lưu trữ giao dịch ACID -complaint. Để cho InnoDB để được giao dịch, nó có để hỗ trợ I
trong ACID
, đó là ly. Kỹ thuật duy trì sự cô lập cho các giao dịch được thực hiện thông qua MVCC, Multiversion Concurrency Control . Nói một cách đơn giản, InnoDB ghi lại dữ liệu trông như thế nào trước khi các giao dịch cố gắng thay đổi chúng. Trường hợp đó được ghi lại? Trong tệp không gian bảng hệ thống, được gọi là ibdata1. Điều đó đòi hỏi I / O đĩa .
Vì cả InnoDB và MyISAM đều thực hiện I / O trên đĩa, yếu tố ngẫu nhiên nào quyết định ai nhanh hơn?
DELETEs
vàUPDATEs
Do đó, trong môi trường đọc nhiều, bảng MyISAM có Định dạng hàng cố định có thể vượt trội hơn so với InnoDB đọc ra khỏi Nhóm đệm InnoDB nếu có đủ dữ liệu được ghi vào nhật ký hoàn tác có trong ibdata1 để hỗ trợ hành vi giao dịch áp đặt trên dữ liệu InnoDB.
Lập kế hoạch loại dữ liệu, truy vấn và công cụ lưu trữ của bạn thực sự cẩn thận. Khi dữ liệu phát triển, việc di chuyển dữ liệu trở nên rất khó khăn. Chỉ cần hỏi Facebook ...
Trong một thế giới đơn giản, MyISAM nhanh hơn để đọc, InnoDB nhanh hơn để viết.
Khi bạn bắt đầu giới thiệu đọc / ghi hỗn hợp, InnoDB cũng sẽ nhanh hơn để đọc, nhờ cơ chế khóa Hàng của nó.
Tôi đã viết một so sánh về các công cụ lưu trữ MySQL vài năm trước, điều đó vẫn đúng cho đến ngày nay, nêu ra những khác biệt duy nhất giữa MyISAM và InnoDB.
Theo kinh nghiệm của tôi, bạn nên sử dụng InnoDB cho tất cả mọi thứ trừ các bảng bộ đệm nặng đọc, trong đó việc mất dữ liệu do tham nhũng không quá quan trọng.
Để thêm vào các câu trả lời ở đây bao gồm sự khác biệt cơ học giữa hai động cơ, tôi trình bày một nghiên cứu so sánh tốc độ theo kinh nghiệm.
Về tốc độ thuần túy, không phải lúc nào MyISAM cũng nhanh hơn InnoDB nhưng theo kinh nghiệm của tôi, nó có xu hướng nhanh hơn đối với môi trường làm việc PURE READ với hệ số khoảng 2,0-2,5 lần. Rõ ràng điều này không phù hợp với mọi môi trường - như những người khác đã viết, MyISAM thiếu những thứ như giao dịch và khóa ngoại.
Tôi đã thực hiện một chút điểm chuẩn bên dưới - Tôi đã sử dụng python để lặp và thư viện timeit để so sánh thời gian. Đối với sở thích, tôi cũng đã bao gồm công cụ bộ nhớ, điều này mang lại hiệu suất tốt nhất trên bảng mặc dù nó chỉ phù hợp với các bảng nhỏ hơn (bạn liên tục gặp phải The table 'tbl' is full
khi vượt quá giới hạn bộ nhớ MySQL). Bốn loại lựa chọn tôi nhìn vào là:
Đầu tiên, tôi đã tạo ba bảng bằng SQL sau
CREATE TABLE
data_interrogation.test_table_myisam
(
index_col BIGINT NOT NULL AUTO_INCREMENT,
value1 DOUBLE,
value2 DOUBLE,
value3 DOUBLE,
value4 DOUBLE,
PRIMARY KEY (index_col)
)
ENGINE=MyISAM DEFAULT CHARSET=utf8
với 'MyISAM' được thay thế cho 'InnoDB' và 'bộ nhớ' trong bảng thứ hai và thứ ba.
Truy vấn: SELECT * FROM tbl WHERE index_col = xx
Kết quả: bốc thăm
Tốc độ của tất cả đều giống nhau, và như mong đợi là tuyến tính trong số lượng cột được chọn. InnoDB có vẻ nhanh hơn MyISAM một chút nhưng điều này thực sự rất khó khăn.
Mã số:
import timeit
import MySQLdb
import MySQLdb.cursors
import random
from random import randint
db = MySQLdb.connect(host="...", user="...", passwd="...", db="...", cursorclass=MySQLdb.cursors.DictCursor)
cur = db.cursor()
lengthOfTable = 100000
# Fill up the tables with random data
for x in xrange(lengthOfTable):
rand1 = random.random()
rand2 = random.random()
rand3 = random.random()
rand4 = random.random()
insertString = "INSERT INTO test_table_innodb (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
insertString2 = "INSERT INTO test_table_myisam (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
insertString3 = "INSERT INTO test_table_memory (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
cur.execute(insertString)
cur.execute(insertString2)
cur.execute(insertString3)
db.commit()
# Define a function to pull a certain number of records from these tables
def selectRandomRecords(testTable,numberOfRecords):
for x in xrange(numberOfRecords):
rand1 = randint(0,lengthOfTable)
selectString = "SELECT * FROM " + testTable + " WHERE index_col = " + str(rand1)
cur.execute(selectString)
setupString = "from __main__ import selectRandomRecords"
# Test time taken using timeit
myisam_times = []
innodb_times = []
memory_times = []
for theLength in [3,10,30,100,300,1000,3000,10000]:
innodb_times.append( timeit.timeit('selectRandomRecords("test_table_innodb",' + str(theLength) + ')', number=100, setup=setupString) )
myisam_times.append( timeit.timeit('selectRandomRecords("test_table_myisam",' + str(theLength) + ')', number=100, setup=setupString) )
memory_times.append( timeit.timeit('selectRandomRecords("test_table_memory",' + str(theLength) + ')', number=100, setup=setupString) )
Truy vấn: SELECT count(*) FROM tbl
Kết quả: MyISAM thắng
Điều này cho thấy sự khác biệt lớn giữa MyISAM và InnoDB - MyISAM (và bộ nhớ) theo dõi số lượng hồ sơ trong bảng, vì vậy giao dịch này nhanh và O (1). Lượng thời gian cần thiết để InnoDB đếm tăng siêu tuyến tính với kích thước bảng trong phạm vi tôi đã điều tra. Tôi nghi ngờ nhiều sự tăng tốc từ các truy vấn MyISAM được quan sát thấy trong thực tế là do các hiệu ứng tương tự.
Mã số:
myisam_times = []
innodb_times = []
memory_times = []
# Define a function to count the records
def countRecords(testTable):
selectString = "SELECT count(*) FROM " + testTable
cur.execute(selectString)
setupString = "from __main__ import countRecords"
# Truncate the tables and re-fill with a set amount of data
for theLength in [3,10,30,100,300,1000,3000,10000,30000,100000]:
truncateString = "TRUNCATE test_table_innodb"
truncateString2 = "TRUNCATE test_table_myisam"
truncateString3 = "TRUNCATE test_table_memory"
cur.execute(truncateString)
cur.execute(truncateString2)
cur.execute(truncateString3)
for x in xrange(theLength):
rand1 = random.random()
rand2 = random.random()
rand3 = random.random()
rand4 = random.random()
insertString = "INSERT INTO test_table_innodb (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
insertString2 = "INSERT INTO test_table_myisam (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
insertString3 = "INSERT INTO test_table_memory (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
cur.execute(insertString)
cur.execute(insertString2)
cur.execute(insertString3)
db.commit()
# Count and time the query
innodb_times.append( timeit.timeit('countRecords("test_table_innodb")', number=100, setup=setupString) )
myisam_times.append( timeit.timeit('countRecords("test_table_myisam")', number=100, setup=setupString) )
memory_times.append( timeit.timeit('countRecords("test_table_memory")', number=100, setup=setupString) )
Truy vấn: SELECT * FROM tbl WHERE value1<0.5 AND value2<0.5 AND value3<0.5 AND value4<0.5
Kết quả: MyISAM thắng
Ở đây, MyISAM và bộ nhớ thực hiện gần như nhau và đánh bại InnoDB khoảng 50% cho các bảng lớn hơn. Đây là loại truy vấn mà lợi ích của MyISAM dường như được tối đa hóa.
Mã số:
myisam_times = []
innodb_times = []
memory_times = []
# Define a function to perform conditional selects
def conditionalSelect(testTable):
selectString = "SELECT * FROM " + testTable + " WHERE value1 < 0.5 AND value2 < 0.5 AND value3 < 0.5 AND value4 < 0.5"
cur.execute(selectString)
setupString = "from __main__ import conditionalSelect"
# Truncate the tables and re-fill with a set amount of data
for theLength in [3,10,30,100,300,1000,3000,10000,30000,100000]:
truncateString = "TRUNCATE test_table_innodb"
truncateString2 = "TRUNCATE test_table_myisam"
truncateString3 = "TRUNCATE test_table_memory"
cur.execute(truncateString)
cur.execute(truncateString2)
cur.execute(truncateString3)
for x in xrange(theLength):
rand1 = random.random()
rand2 = random.random()
rand3 = random.random()
rand4 = random.random()
insertString = "INSERT INTO test_table_innodb (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
insertString2 = "INSERT INTO test_table_myisam (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
insertString3 = "INSERT INTO test_table_memory (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
cur.execute(insertString)
cur.execute(insertString2)
cur.execute(insertString3)
db.commit()
# Count and time the query
innodb_times.append( timeit.timeit('conditionalSelect("test_table_innodb")', number=100, setup=setupString) )
myisam_times.append( timeit.timeit('conditionalSelect("test_table_myisam")', number=100, setup=setupString) )
memory_times.append( timeit.timeit('conditionalSelect("test_table_memory")', number=100, setup=setupString) )
Kết quả: InnoDB thắng
Đối với truy vấn này, tôi đã tạo một tập hợp các bảng bổ sung cho lựa chọn phụ. Mỗi cột chỉ đơn giản là hai cột BIGINT, một cột có chỉ mục khóa chính và một cột không có chỉ mục. Do kích thước bảng lớn, tôi đã không kiểm tra bộ nhớ. Lệnh tạo bảng SQL là
CREATE TABLE
subselect_myisam
(
index_col bigint NOT NULL,
non_index_col bigint,
PRIMARY KEY (index_col)
)
ENGINE=MyISAM DEFAULT CHARSET=utf8;
trong đó một lần nữa, 'MyISAM' được thay thế cho 'InnoDB' trong bảng thứ hai.
Trong truy vấn này, tôi để kích thước của bảng chọn ở mức 1000000 và thay vào đó thay đổi kích thước của các cột được chọn phụ.
Ở đây, InnoDB thắng dễ dàng. Sau khi chúng ta có được một bảng kích thước hợp lý, cả hai động cơ đều có tỷ lệ tuyến tính với kích thước của phần chọn phụ. Chỉ mục tăng tốc lệnh MyISAM nhưng thú vị là ít ảnh hưởng đến tốc độ InnoDB. subSelect.png
Mã số:
myisam_times = []
innodb_times = []
myisam_times_2 = []
innodb_times_2 = []
def subSelectRecordsIndexed(testTable,testSubSelect):
selectString = "SELECT * FROM " + testTable + " WHERE index_col in ( SELECT index_col FROM " + testSubSelect + " )"
cur.execute(selectString)
setupString = "from __main__ import subSelectRecordsIndexed"
def subSelectRecordsNotIndexed(testTable,testSubSelect):
selectString = "SELECT * FROM " + testTable + " WHERE index_col in ( SELECT non_index_col FROM " + testSubSelect + " )"
cur.execute(selectString)
setupString2 = "from __main__ import subSelectRecordsNotIndexed"
# Truncate the old tables, and re-fill with 1000000 records
truncateString = "TRUNCATE test_table_innodb"
truncateString2 = "TRUNCATE test_table_myisam"
cur.execute(truncateString)
cur.execute(truncateString2)
lengthOfTable = 1000000
# Fill up the tables with random data
for x in xrange(lengthOfTable):
rand1 = random.random()
rand2 = random.random()
rand3 = random.random()
rand4 = random.random()
insertString = "INSERT INTO test_table_innodb (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
insertString2 = "INSERT INTO test_table_myisam (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
cur.execute(insertString)
cur.execute(insertString2)
for theLength in [3,10,30,100,300,1000,3000,10000,30000,100000]:
truncateString = "TRUNCATE subselect_innodb"
truncateString2 = "TRUNCATE subselect_myisam"
cur.execute(truncateString)
cur.execute(truncateString2)
# For each length, empty the table and re-fill it with random data
rand_sample = sorted(random.sample(xrange(lengthOfTable), theLength))
rand_sample_2 = random.sample(xrange(lengthOfTable), theLength)
for (the_value_1,the_value_2) in zip(rand_sample,rand_sample_2):
insertString = "INSERT INTO subselect_innodb (index_col,non_index_col) VALUES (" + str(the_value_1) + "," + str(the_value_2) + ")"
insertString2 = "INSERT INTO subselect_myisam (index_col,non_index_col) VALUES (" + str(the_value_1) + "," + str(the_value_2) + ")"
cur.execute(insertString)
cur.execute(insertString2)
db.commit()
# Finally, time the queries
innodb_times.append( timeit.timeit('subSelectRecordsIndexed("test_table_innodb","subselect_innodb")', number=100, setup=setupString) )
myisam_times.append( timeit.timeit('subSelectRecordsIndexed("test_table_myisam","subselect_myisam")', number=100, setup=setupString) )
innodb_times_2.append( timeit.timeit('subSelectRecordsNotIndexed("test_table_innodb","subselect_innodb")', number=100, setup=setupString2) )
myisam_times_2.append( timeit.timeit('subSelectRecordsNotIndexed("test_table_myisam","subselect_myisam")', number=100, setup=setupString2) )
Tôi nghĩ rằng thông điệp mang về nhà của tất cả những điều này là nếu bạn thực sự quan tâm đến tốc độ, bạn cần phải đánh giá các truy vấn mà bạn đang thực hiện thay vì đưa ra bất kỳ giả định nào về động cơ nào sẽ phù hợp hơn.
SELECT * FROM tbl WHERE index_col = xx
- Dưới đây là hai yếu tố có khả năng dẫn đến sự thay đổi nhiều hơn trong biểu đồ: Khóa chính so với khóa phụ; chỉ mục được lưu trữ so với không.
SELECT COUNT(*)
là một người chiến thắng rõ ràng cho MyISAM cho đến khi bạn thêm một WHERE
điều khoản.
Cái nào nhanh hơn? Hoặc có thể nhanh hơn. YMMV.
Bạn nên sử dụng loại nào? InnoDB - an toàn khi va chạm, v.v.