Làm cách nào để cập nhật 10 triệu + hàng trong bảng đơn MySQL nhanh nhất có thể?


32

Sử dụng MySQL 5.6 với công cụ lưu trữ InnoDB cho hầu hết các bảng. Kích thước nhóm bộ đệm của InnoDB là 15 GB và các chỉ số Innodb DB + là khoảng 10 GB. Máy chủ có RAM 32 GB và đang chạy Cent OS 7 x64.

Tôi có một bảng lớn chứa khoảng 10 triệu + hồ sơ.

Tôi nhận được một tệp kết xuất được cập nhật từ một máy chủ từ xa cứ sau 24 giờ. Các tập tin có định dạng csv. Tôi không có quyền kiểm soát định dạng đó. Tệp có dung lượng ~ 750 MB. Tôi đã thử chèn dữ liệu vào một hàng của bảng MyISAM theo hàng và mất 35 phút.

Tôi chỉ cần lấy 3 giá trị trên mỗi dòng trong số 10-12 từ tệp và cập nhật nó trong cơ sở dữ liệu.

Cách tốt nhất để đạt được một cái gì đó như thế này là gì?

Tôi cần phải làm điều này hàng ngày.

Hiện tại Flow là như thế này:

  1. mysqli_begin_transaction
  2. Đọc tệp kết xuất theo từng dòng
  3. Cập nhật từng bản ghi Từng dòng.
  4. mysqli_commit

Các hoạt động trên mất khoảng 30 đến 40 phút để hoàn thành và trong khi thực hiện việc này, có những cập nhật khác đang diễn ra mang lại cho tôi

Vượt quá thời gian chờ khóa; thử khởi động lại giao dịch

Cập nhật 1

tải dữ liệu trong bảng mới bằng cách sử dụng LOAD DATA LOCAL INFILE. Trong MyISAM, phải mất 38.93 sectrong InnoDB, mất 7 phút 5,21 giây. Sau đó, tôi đã làm:

UPDATE table1 t1, table2 t2
SET 
t1.field1 = t2.field1,
t1.field2 = t2.field2,
t1.field3 = t2.field3
WHERE t1.field10 = t2.field10

Query OK, 434914 rows affected (22 hours 14 min 47.55 sec)

Cập nhật 2

cùng cập nhật với truy vấn tham gia

UPDATE table1 a JOIN table2 b 
ON a.field1 = b.field1 
SET 
a.field2 = b.field2,
a.field3 = b.field3,
a.field4 = b.field4

(14 hours 56 min 46.85 sec)

Làm rõ từ các câu hỏi trong ý kiến:

  • Khoảng 6% các hàng trong bảng sẽ được cập nhật bởi tệp, nhưng đôi khi nó có thể lên tới 25%.
  • Có các chỉ mục trên các lĩnh vực đang được cập nhật. Có 12 chỉ mục trên bảng và 8 chỉ mục bao gồm các trường cập nhật.
  • Không cần thiết phải thực hiện cập nhật trong một giao dịch. Nó có thể mất thời gian nhưng không quá 24 giờ. Tôi đang tìm cách hoàn thành nó trong 1 giờ mà không khóa toàn bộ bảng, vì sau này tôi phải cập nhật chỉ số nhân sư phụ thuộc vào bảng này. Không thành vấn đề nếu các bước mất nhiều thời gian hơn miễn là cơ sở dữ liệu có sẵn cho các tác vụ khác.
  • Tôi có thể sửa đổi định dạng csv trong bước tiền xử lý. Điều duy nhất quan trọng là cập nhật nhanh chóng và không khóa.
  • Bảng 2 là MyISAM. Đây là bảng mới được tạo từ tệp csv bằng cách sử dụng dữ liệu tải dữ liệu. Kích thước tệp MYI là 452 MB. Bảng 2 được lập chỉ mục trên cột field1.
  • MYD của bảng MyISAM là 663MB.

Cập nhật 3:

Dưới đây là chi tiết về cả hai bảng.

CREATE TABLE `content` (
  `hash` char(40) CHARACTER SET ascii NOT NULL DEFAULT '',
  `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
  `og_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
  `keywords` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
  `files_count` smallint(5) unsigned NOT NULL DEFAULT '0',
  `more_files` smallint(5) unsigned NOT NULL DEFAULT '0',
  `files` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '0',
  `category` smallint(3) unsigned NOT NULL DEFAULT '600',
  `size` bigint(19) unsigned NOT NULL DEFAULT '0',
  `downloaders` int(11) NOT NULL DEFAULT '0',
  `completed` int(11) NOT NULL DEFAULT '0',
  `uploaders` int(11) NOT NULL DEFAULT '0',
  `creation_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `upload_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `last_updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `vote_up` int(11) unsigned NOT NULL DEFAULT '0',
  `vote_down` int(11) unsigned NOT NULL DEFAULT '0',
  `comments_count` int(11) NOT NULL DEFAULT '0',
  `imdb` int(8) unsigned NOT NULL DEFAULT '0',
  `video_sample` tinyint(1) NOT NULL DEFAULT '0',
  `video_quality` tinyint(2) NOT NULL DEFAULT '0',
  `audio_lang` varchar(127) CHARACTER SET ascii NOT NULL DEFAULT '',
  `subtitle_lang` varchar(127) CHARACTER SET ascii NOT NULL DEFAULT '',
  `verified` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `uploader` int(11) unsigned NOT NULL DEFAULT '0',
  `anonymous` tinyint(1) NOT NULL DEFAULT '0',
  `enabled` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `tfile_size` int(11) unsigned NOT NULL DEFAULT '0',
  `scrape_source` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `record_num` int(11) unsigned NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`record_num`),
  UNIQUE KEY `hash` (`hash`),
  KEY `uploaders` (`uploaders`),
  KEY `tfile_size` (`tfile_size`),
  KEY `enabled_category_upload_date_verified_` (`enabled`,`category`,`upload_date`,`verified`),
  KEY `enabled_upload_date_verified_` (`enabled`,`upload_date`,`verified`),
  KEY `enabled_category_verified_` (`enabled`,`category`,`verified`),
  KEY `enabled_verified_` (`enabled`,`verified`),
  KEY `enabled_uploader_` (`enabled`,`uploader`),
  KEY `anonymous_uploader_` (`anonymous`,`uploader`),
  KEY `enabled_uploaders_upload_date_` (`enabled`,`uploaders`,`upload_date`),
  KEY `enabled_verified_category` (`enabled`,`verified`,`category`),
  KEY `verified_enabled_category` (`verified`,`enabled`,`category`)
) ENGINE=InnoDB AUTO_INCREMENT=7551163 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=FIXED


CREATE TABLE `content_csv_dump_temp` (
  `hash` char(40) CHARACTER SET ascii NOT NULL DEFAULT '',
  `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `category_id` int(11) unsigned NOT NULL DEFAULT '0',
  `uploaders` int(11) unsigned NOT NULL DEFAULT '0',
  `downloaders` int(11) unsigned NOT NULL DEFAULT '0',
  `verified` tinyint(1) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`hash`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

và đây là truy vấn cập nhật contentbảng cập nhật sử dụng dữ liệu từcontent_csv_dump_temp

UPDATE content a JOIN content_csv_dump_temp b 
ON a.hash = b.hash 
SET 
a.uploaders = b.uploaders,
a.downloaders = b.downloaders,
a.verified = b.verified

cập nhật 4:

tất cả các thử nghiệm trên đã được thực hiện trên máy thử nghiệm. Nhưng bây giờ tôi đã thực hiện các thử nghiệm tương tự trên máy sản xuất và các truy vấn rất nhanh.

mysql> UPDATE content_test a JOIN content_csv_dump_temp b
    -> ON a.hash = b.hash
    -> SET
    -> a.uploaders = b.uploaders,
    -> a.downloaders = b.downloaders,
    -> a.verified = b.verified;
Query OK, 2673528 rows affected (7 min 50.42 sec)
Rows matched: 7044818  Changed: 2673528  Warnings: 0

tôi xin lỗi vì lỗi lầm của tôi. Nó tốt hơn để sử dụng tham gia thay vì mỗi bản cập nhật hồ sơ. bây giờ tôi đang cố gắng cải thiện mpre bằng cách sử dụng chỉ mục được đề xuất bởi rick_james, sẽ cập nhật sau khi đánh dấu băng ghế được thực hiện.


Bạn có một hỗn hợp INDEX(field2, field3, field4) (theo thứ tự nào) không? Xin hãy chỉ cho chúng tôi SHOW CREATE TABLE.
Rick James

1
Chỉ số 12 và 8 là một phần nghiêm trọng trong vấn đề của bạn. MyISAM là một phần nghiêm trọng khác. InnoDB hoặc TokuDB hoạt động tốt hơn nhiều với nhiều chỉ mục.
Rick James

Bạn có hai khác nhau UPDATEs . Vui lòng cho chúng tôi biết chính xác câu lệnh đơn giản trông như thế nào khi cập nhật bảng từ dữ liệu csv. Sau đó, chúng tôi có thể giúp bạn nghĩ ra một kỹ thuật đáp ứng yêu cầu của bạn.
Rick James

@RickJames chỉ có một update, và vui lòng kiểm tra câu hỏi được cập nhật., Cảm ơn
AMB

Câu trả lời:


17

Dựa trên kinh nghiệm của tôi, tôi sẽ sử dụng LOAD DATA INFILE để nhập tệp CSV của bạn.

Câu lệnh LOAD DATA INFILE đọc các hàng từ tệp văn bản vào bảng với tốc độ rất cao.

Ví dụ tôi tìm thấy trên ví dụ Tải dữ liệu internet . Tôi đã thử nghiệm ví dụ này trên hộp của tôi và hoạt động tốt

Bảng ví dụ

CREATE TABLE example (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `Column2` varchar(14) NOT NULL,
  `Column3` varchar(14) NOT NULL,
  `Column4` varchar(14) NOT NULL,
  `Column5` DATE NOT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB

Ví dụ tệp CSV

# more /tmp/example.csv
Column1,Column2,Column3,Column4,Column5
1,A,Foo,sdsdsd,4/13/2013
2,B,Bar,sdsa,4/12/2013
3,C,Foo,wewqe,3/12/2013
4,D,Bar,asdsad,2/1/2013
5,E,FOObar,wewqe,5/1/2013

Nhập báo cáo để được chạy từ bảng điều khiển MySQL

LOAD DATA LOCAL INFILE '/tmp/example.csv'
    -> INTO TABLE example
    -> FIELDS TERMINATED BY ','
    -> LINES TERMINATED BY '\n'
    -> IGNORE 1 LINES
    -> (id, Column3,Column4, @Column5)
    -> set
    -> Column5 = str_to_date(@Column5, '%m/%d/%Y');

Kết quả

MySQL [testcsv]> select * from example;
+----+---------+---------+---------+------------+
| Id | Column2 | Column3 | Column4 | Column5    |
+----+---------+---------+---------+------------+
|  1 |         | Column2 | Column3 | 0000-00-00 |
|  2 |         | B       | Bar     | 0000-00-00 |
|  3 |         | C       | Foo     | 0000-00-00 |
|  4 |         | D       | Bar     | 0000-00-00 |
|  5 |         | E       | FOObar  | 0000-00-00 |
+----+---------+---------+---------+------------+

IGNORE chỉ đơn giản là bỏ qua dòng đầu tiên là tiêu đề cột.

Sau IGNORE, chúng tôi sẽ chỉ định các cột (bỏ qua cột2), để nhập, phù hợp với một trong các tiêu chí trong câu hỏi của bạn.

Đây là một ví dụ khác trực tiếp từ Oracle: ví dụ LOAD DATA INFILE

Điều này là đủ để bạn bắt đầu.


tôi có thể sử dụng tải dữ liệu để tải dữ liệu trong bảng tạm thời và sau đó sử dụng các truy vấn khác để cập nhật dữ liệu trong bảng chính., cảm ơn
AMB

14

Trong tất cả những điều được đề cập, có vẻ như nút cổ chai là sự tham gia của chính nó.

NHIỆM VỤ # 1: Tham gia Kích thước bộ đệm

Trong tất cả khả năng, jo_buffer_size của bạn có thể quá thấp.

Theo Tài liệu MySQL về cách MySQL sử dụng bộ đệm tham gia bộ đệm

Chúng tôi chỉ lưu trữ các cột được sử dụng trong bộ đệm tham gia, không phải toàn bộ các hàng.

Đây là trường hợp, làm cho các phím của bộ đệm tham gia nằm trong RAM.

Bạn có 10 triệu hàng nhân 4 byte cho mỗi khóa. Đó là khoảng 40 triệu.

Hãy thử nâng nó lên trong phiên tới 42M (lớn hơn một chút so với 40 triệu)

SET join_buffer_size = 1024 * 1024 * 42;
UPDATE table1 a JOIN table2 b 
ON a.field1 = b.field1 
SET 
a.field2 = b.field2,
a.field3 = b.field3,
a.field4 = b.field4;

Nếu đây là một mẹo nhỏ, hãy tiến hành thêm nó vào my.cnf

[mysqld]
join_buffer_size = 42M

Khởi động lại mysqld không cần thiết cho các kết nối mới. Chỉ cần chạy

mysql> SET GLOBAL join_buffer_size = 1024 * 1024 * 42;

NHIỆM VỤ # 2: Tham gia hoạt động

Bạn có thể điều khiển kiểu của thao tác nối bằng cách chỉnh lại trình tối ưu hóa

Theo Tài liệu MySQL về Tham gia truy cập khóa khối lồng nhau và vòng lặp

Khi BKA được sử dụng, giá trị của jo_buffer_size xác định lô khóa lớn trong mỗi yêu cầu đối với công cụ lưu trữ. Bộ đệm càng lớn, truy cập tuần tự sẽ càng nhiều vào bảng bên phải của một hoạt động nối, có thể cải thiện đáng kể hiệu suất.

Để BKA được sử dụng, cờ batched_key_access của biến hệ thống Optimizer_switch phải được đặt thành bật. BKA sử dụng MRR, do đó cờ mrr cũng phải được bật. Hiện tại, ước tính chi phí cho MRR là quá bi quan. Do đó, cũng cần thiết để mrr_cost_basing tắt để BKA được sử dụng.

Trang này cũng khuyên bạn nên làm điều này:

mysql> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';

NHIỆM VỤ # 3: Viết cập nhật vào đĩa (TÙY CHỌN)

Hầu hết quên tăng innodb_write_io_threads để viết các trang bẩn ra khỏi vùng đệm nhanh hơn.

[mysqld]
innodb_write_io_threads = 16

Bạn sẽ phải khởi động lại MySQL để thay đổi

HÃY THỬ MỘT LẦN !!!


Tốt đẹp! +1 cho mẹo đệm tham gia điều chỉnh. Nếu bạn phải tham gia, tham gia vào bộ nhớ. Mẹo tốt!
Peter Dixon-Moses

3
  1. CREATE TABLE phù hợp với CSV
  2. LOAD DATA vào cái bàn đó
  3. UPDATE real_table JOIN csv_table ON ... SET ..., ..., ...;
  4. DROP TABLE csv_table;

Bước 3 sẽ nhanh hơn rất nhiều so với từng hàng, nhưng nó vẫn sẽ khóa tất cả các hàng trong bảng trong một khoảng thời gian không hề nhỏ. Nếu thời gian khóa này quan trọng hơn thời gian toàn bộ quá trình thì ...

Nếu không có gì khác được viết lên bàn, thì ...

  1. CREATE TABLEphù hợp với CSV; không chỉ ngoại trừ những gì là cần thiết trong JOINtrong UPDATE. Nếu độc đáo, làm cho nó PRIMARY KEY.
  2. LOAD DATA vào cái bàn đó
  3. sao chép real_tablevào new_table( CREATE ... SELECT)
  4. UPDATE new_table JOIN csv_table ON ... SET ..., ..., ...;
  5. RENAME TABLE real_table TO old, new_table TO real_table;
  6. DROP TABLE csv_table, old;

Bước 3 nhanh hơn cập nhật, đặc biệt là nếu các chỉ mục không cần thiết bị bỏ đi.
Bước 5 là "tức thời".


giả sử trong vài giây, sau bước 3, chúng ta đang thực hiện bước 4, sau đó dữ liệu mới được chèn vào real_table để chúng ta sẽ bỏ lỡ dữ liệu đó trong new_table? cách giải quyết cho việc này là gì? cảm ơn
AMB

Xem những gì pt-online-schema-digest; nó quan tâm đến các vấn đề như vậy thông qua a TRIGGER.
Rick James

Bạn có thể không cần bất kỳ chỉ mục trên bảng từ LOAD DATA. Thêm các chỉ mục không cần thiết là tốn kém (trong thời gian).
Rick James

Dựa trên thông tin mới nhất, tôi nghiêng về tệp CSV được tải vào bảng MyISAM chỉ bằng một AUTO_INCREMENT, sau đó phân đoạn 1K hàng mỗi lần dựa trên PK. Nhưng tôi cần phải xem tất cả các yêu cầu lược đồ bảng trước khi cố gắng đánh vần các chi tiết.
Rick James

tôi đã thiết lập băm như PRIMARY index, nhưng trong khi chunking trong 50k sử dụng truy vấn để mất nhiều thời gian., nó sẽ là tốt hơn nếu tôi tạo ra tự động tăng? và đặt nó là PRIMARY index?
AMB

3

Bạn đã nói:

  • Cập nhật ảnh hưởng đến 6-25% bảng của bạn
  • Bạn muốn làm điều này nhanh nhất có thể (<1hr)
  • không khóa
  • nó không phải trong một giao dịch
  • chưa (trong nhận xét về câu trả lời của Rick James), bạn bày tỏ mối quan tâm về điều kiện chủng tộc

Nhiều trong số những tuyên bố này có thể mâu thuẫn. Ví dụ, các bản cập nhật lớn không khóa bảng. Hoặc tránh các điều kiện cuộc đua không sử dụng một giao dịch khổng lồ.

Ngoài ra, do bảng của bạn được lập chỉ mục nhiều, cả chèn và cập nhật đều có thể chậm.


Tránh điều kiện chủng tộc

Nếu bạn có thể thêm dấu thời gian cập nhật vào bảng của mình, bạn có thể giải quyết các điều kiện cuộc đua đồng thời tránh ghi nhật ký nửa triệu cập nhật trong một giao dịch.

Điều này giải phóng bạn để thực hiện cập nhật từng dòng (như bạn hiện đang làm), nhưng với các đợt giao dịch tự động hoặc hợp lý hơn.

Bạn tránh các điều kiện cuộc đua (trong khi cập nhật từng dòng một) bằng cách thực hiện kiểm tra xem bản cập nhật sau này chưa xảy ra ( UPDATE ... WHERE pk = [pk] AND updated < [batchfile date])

Và quan trọng là, điều này cho phép bạn chạy các bản cập nhật song song .


Chạy càng nhanh càng tốt.

Với thời gian này kiểm tra tem tại chỗ:

  1. Chia tệp bó của bạn thành một số khối có kích thước hợp lý (giả sử 50.000 hàng / tệp)
  2. Song song, có một tập lệnh được đọc trong mỗi tệp và xuất ra một tệp có 50.000 câu lệnh CẬP NHẬT.
  3. Song song, một khi (2) kết thúc, đã mysqlchạy từng tệp sql.

(ví dụ: bashNhìn vào splitxargs -Pđể biết cách dễ dàng chạy lệnh theo nhiều cách song song. Mức độ song song phụ thuộc vào số lượng luồng bạn muốn dành cho bản cập nhật )


Hãy nhớ rằng "từng dòng một" có thể chậm hơn gấp 10 lần so với thực hiện theo lô ít nhất 100.
Rick James

Bạn phải đánh giá nó trong trường hợp này để chắc chắn. Cập nhật 6-25% của một bảng (với 8 chỉ mục liên quan đến các cột được cập nhật), tôi sẽ giải trí khả năng bảo trì chỉ mục trở thành nút cổ chai.
Peter Dixon-Moses

Ý tôi là, trong một số trường hợp, có thể nhanh hơn để thả các chỉ mục, cập nhật hàng loạt và tạo lại chúng sau ... nhưng OP không muốn thời gian chết.
Peter Dixon-Moses

1

Cập nhật lớn bị ràng buộc I / O. Tôi muốn đề nghị:

  1. Tạo một bảng riêng biệt sẽ lưu trữ 3 trường được cập nhật thường xuyên của bạn. Chúng ta hãy gọi một bảng tài sản_static nơi bạn lưu giữ, tốt, dữ liệu tĩnh và các tài sản khác_dynamic sẽ lưu trữ trình tải lên, trình tải xuống và xác minh.
  2. Nếu bạn có thể, hãy sử dụng công cụ MEMOR cho bảng tài sản_dynamic . (sao lưu vào đĩa sau mỗi lần cập nhật).
  3. Cập nhật nhẹ và nhanh nhẹn của bạn assets_dynamic theo cập nhật của bạn 4 (tức TẢI INFILE ... INTO temp; CẬP NHẬT assets_dynamic lệnh JOIN tạm b trên a.id = b.id SET [những gì đã được cập nhật] này nên mất ít hơn một. phút. (Trên hệ thống của chúng tôi, tài sản_dynamic95M hàng và cập nhật tác động ~ 6 triệu hàng, trong hơn 40 giây một chút.)
  4. Khi bạn chạy bộ chỉ mục của Sphinx, THAM GIA tài sản_static và tài sản_dynamic (giả sử rằng bạn muốn sử dụng một trong các trường này làm thuộc tính).

0

Để UPDATEchạy nhanh, bạn cần

INDEX(uploaders, downloaders, verified)

Nó có thể ở trên một trong hai bàn. Ba lĩnh vực có thể theo thứ tự bất kỳ.

Điều đó sẽ tạo điều kiện cho việc UPDATEcó thể nhanh chóng khớp các hàng giữa hai bảng.

làm cho các kiểu dữ liệu giống nhau trong hai bảng (cả hai INT SIGNEDhoặc cả hai INT UNSIGNED).


Điều này thực sự làm chậm cập nhật.
AMB

Hmmm ... Vui lòng cung cấp EXPLAIN UPDATE ...;.
Rick James
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.