MySQL Insert/Update nhiều record hiệu quả

Xin chào các bạn, mình viết bài này để share kinh nghiệm insert hoặc update nhiều dòng dữ liệu với database MySQL.

Bài toán:

mình cần phải thêm nhiều dòng vào DB, nếu dữ liệu đó đã tồn tại thì update một cột nào đó thôi.

Query insert/update từng dòng

Cách đơn giản nhất là query từng dòng, kiểm tra trùng và update. Ở đây mình dùng Django ORM phiên bản 1.6 chỉ có hàm get_or_create kiểm tra xem dữ liệu có chưa, nếu chưa có thì tạo.

for record in records:
    update_time = random.randint(0, 100)
    record, created = VideoTab.objects.get_or_create(
        match_id=record.match_id,
        round_num=record.round_num,
        video_id=record.video_id,
        platform=record.platform,
        defaults={
            'status': record.status,
            'create_time': record.create_time,
            'update_time': update_time,
        },
    )
    if not created:
        # nếu đã tạo rồi thì update cái update_time thôi
        record.update_time = update_time
        record.save()

Làm như thế cũng được, nhưng rất chậm bởi vì mình cần phải gửi yêu cầu lên DB cho mỗi dòng dữ liệu và chờ kiểm tra xử lý.

query update nhiều dòng

Cách hiệu quả hơn đó là dùng MysqlClient.execute_many

DB = MysqlClient(...) #thông tin config db

records = self.get_all_records()
my_raw_sql = """
	INSERT INTO my_table (video_id, platform, match_id, round_num, status, create_time, update_time)
	VALUES (%s, %s, %s, %s, %s, %s, %s)
	ON DUPLICATE KEY UPDATE update_time=VALUES(update_time)
"""

limit = 1000
data = list()
num_report = len(records)
for i, record in enumerate(records):
	update_time = random.randint(0, 100)
	row = (
		record.video_id, record.platform, record.match_id, record.round_num, record.status, record.create_time, update_time
	)
	data.append(row)
	if i + 1 == num_report or (i + 1) % limit == 0:
		DB.execute_many(my_raw_sql, data)
		data = list()

Với code này thì mình sẽ dồn nhiều record lại rồi gửi lên DB một lần. Trong ví dụ trên thì Đạt để limit = 1000, tức là dồn 1000 record lại rồi gửi lên DB cho nó chạy một lần, như thế nhanh hơn.

lúc lệnh execute_many được thực thi thì dữ liệu được đẩy vào DB sẽ trông giống thế này

INSERT INTO my_table (video_id, platform, match_id, round_num, status, create_time, update_time) 
VALUES 
('abc', 1, 396, 1, 0, 1466495556, 90),
...
('xyz', 2, 831, 2, 0, 1489060345, 48)

ON DUPLICATE KEY UPDATE update_time=VALUES(update_time)

So sánh kết quả

Như kết quả so sánh thì cách dùng execute_many nhanh hơn tới 66 lần với tầm 3000 dòng dữ liệu :wink:

7 Likes

Chính xác, bởi vì lệnh MERGE không có trong MySQL nên việc sử dụng ON DUPLICATE KEY rất hợp lý.

Mình thấy đại ý của bài này là gộp nhiều record vào 1 request DB chứ không phải vấn đề dùng ON DUPLICATE KEY @@
ON DUPLICATE KEY được sử dụng là do hàm get_or_create

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?