Tối ưu câu lệnh Insert trong MySql và Laravel

26th Nov 2022
Hiểu được những vấn đề có thể ảnh hưởng đến hiệu năng của câu lệnh Insert
Table of contents

Tối ưu câu lệnh Insert trong Production – cải thiện hơn 17 lần nếu áp dụng đúng kỹ thuật

Giá trị mà bạn nhận được trong bài viết này:

1. Hiểu được những vấn đề có thể ảnh hưởng đến hiệu năng của câu lệnh Insert

2. Biết được một trong số các kỹ thuật tối ưu câu lênh Insert đang được thực hiện trong các dự án tại ngân hàng, chứng khoán, bệnh viện....

1. Cậu lệnh Insert thực hiện lâu nhưng nguyên nhân gốc có thể nằm ở chỗ khác?

Không phải bất kỳ câu lệnh Insert nào thực hiện lâu cũng do nội tại của câu lệnh, anh em có thể thực hiện kiểm tra ngay 2 vấn đề sau

  • Thứ nhất: Có Trigger nào được gọi khi câu lệnh Insert của chúng ta thực thi hay không
  • Thứ hai: Kiểm tra câu lệnh có đang bị LOCK không

1.1. Kiểm tra trigger trong Cơ sở dữ liệu thế nào?

Kiểm tra toàn bộ danh sách Trigger trong Cơ sở dữ liệu

select * from dba_triggers;

Kiểm tra các trigger có liên quan đến TABLE mà chúng ta đang thực hiện lệnh INSERT

select * from dba_triggers where table_name='<TABLE_NAME>’;

1.2. Kiểm tra LOCK trong cơ sở dữ liệu

Tất cả các cơ sở dữ liệu đều có cơ chế LOCK (để bảo vệ tính toàn vẹn dữ liệu) khi các transaction có thực hiên lệnh DML (Delete, Insert, Update).

Hãy xem ví dụ sau:

Thực hiện tạo table tên là WECOMMIT_TABLE phục vụ việc Demo

create table wecommit_table(id number primary key, name varchar2(100));

Chúng ta tạo 2 transaction phục vụ việc Demo này

  • Tiến trình thứ nhất (bên tay trái trong ảnh) thực hiện lệnh vào thời điểm 10h. Câu lệnh này thực hiện được ngay lập tức.
SQL> insert into wecommit_table values(‘1′,’Wecommit1’);

1 row created.

  • Tiến trình thứ hai (bên tay phải trong ảnh) thực hiện sau tiến trình thứ nhất 1 phút. Câu lệnh cũng vô cùng đơn giản
SQL> insert into wecommit_table values (‘1′,’Wecommit_Pending’);
  • Kết quả: Tiến trình thứ hai bị TREO (do hệ thống đang LOCK vì cùng Insert một giá trị Primary Key của bảng). Nếu trường hợp này người dùng chúng ta đang đứng ở phiên làm việc thứ 2 sẽ có cảm giác “KHÔNG THỂ HIỂU NỔI, TẠI SAO INSERT CÓ 1 BẢN GHI MÀ LẠI CHẬM VÃI CHƯỞNG!!!!”
Tối ưu câu lệnh Insert trong Production

2. Kỹ thuât sử dụng HINT để tối ưu câu lệnh Insert

2.1. Phương án 1: Thực hiên INSERT dữ liệu từ kết quả của câu lệnh SELECT

Tại đây tôi thực hiện tạo 1 bảng có cấu trúc giống với bảng WECOMMIT_TABLE

CREATE TABLE HUYTQ.WECOMMIT_TABLE_NEW
(
ID NUMBER,
NAME VARCHAR2(100 BYTE)
);

SQL> insert into WECOMMIT_TABLE_NEW select * from WECOMMIT_TABLE;

1000000 rows created.

Elapsed: 00:00:01.13

SQL> commit;

Commit complete.

2.2. Phương án 2: Thực hiện INSERT nhưng thêm HINT APPEND

SQL> insert /*+ APPEND*/ into WECOMMIT_TABLE_NEW select * from WECOMMIT_TABLE;

1000000 rows created.

Elapsed: 00:00:00.60

Commit complete.

Elapsed: 00:00:00.00

2.3. Kết luận

Sử dụng HINT APPEND đã giúp câu lệnh INSERT cải thiện hơn 2 lần.

Bản chất HINT APPEND này là gì và tại sao có thể cải thiện hiệu năng sẽ được tôi phân tích riêng trên kênh trao đổi của nhóm đặc quyền.

3. Kỹ thuật tối ưu câu lệnh Insert buộc phải biết

Thực hiện 1 triệu lần, mỗi lần Insert 1 bản ghi hay thực hiện 1 lần những Insert 1 triệu bản ghi?

Hãy giảm thiểu số lần gọi câu lệnh SQL đến Database.

Các ví dụ dưới đây tôi sẽ áp dụng kỹ thuật sử dụng 1 bảng TEMP với câu lệnh WITH

WITH
temp
AS
( SELECT LEVEL x
FROM DUAL
CONNECT BY LEVEL <= 1000000)
SELECT ROWNUM id, ‘Wecommit ‘ || ROWNUM AS name
FROM temp

Câu lệnh này bản chất sẽ tạo ra 1 bảng TEMP có dữ liệu như sau:

  • Bảng này có 1.000.000 bản ghi
  • Bảng có 2 cột: Cột ID và Cột Name
  • Cột ID nhận giá trị từ 1 đến 1.000.000
  • Cột Name nhận giá trị kết hợp của chuỗi Wecommit + ID
Tối ưu câu lệnh Insert trong Production

3.1 Phương án 1Thực hiện Insert 1 triệu lần, mỗi lần 1 bản ghi

Thực hiện Insert vào bảng WECOMMIT_TABLE như sau

BEGIN
FOR i IN (WITH
temp
AS
( SELECT LEVEL x
FROM DUAL
CONNECT BY LEVEL <= 1000000)
SELECT ROWNUM id, ‘Wecommit ‘ || ROWNUM AS name
FROM temp)
LOOP
INSERT INTO WECOMMIT_TABLE
VALUES (i.id, i.name);
END LOOP;
END;
/

PL/SQL procedure successfully completed.

Elapsed: 00:00:44.56

5.2. Thực hiện 1 lần Insert nhưng Insert 1 triệu bản ghi

Thực hiện Truncate Table WECOMMIT_TABLE để đảm bảo tính khách quan trước khi Demo

SQL> truncate table WECOMMIT_TABLE;

Table truncated.

INSERT INTO WECOMMIT_TABLE
WITH
temp
AS
( SELECT LEVEL x
FROM DUAL
CONNECT BY LEVEL <= 1000000)
SELECT ROWNUM id, ‘Wecommit ‘ || ROWNUM AS name
FROM temp;

1000000 rows created.

Elapsed: 00:00:04.76

3.3. Nếu áp dụng thêm chiêu thức HINT APPEND bên trên thì sao?

SQL> truncate table WECOMMIT_TABLE;

Table truncated.

SQL> INSERT /*+ APPEND*/
INTO WECOMMIT_TABLE
WITH
temp
AS
( SELECT LEVEL x
FROM DUAL
CONNECT BY LEVEL <= 1000000)
SELECT ROWNUM id, ‘Wecommit ‘ || ROWNUM AS name
FROM temp 2 3 4 5 6 7 8 9 10 ;

1000000 rows created.

Elapsed: 00:00:02.68

Tại đây chúng ta thấy: Cùng 1 mục đích insert 1 triệu giá trị vào bảng WECOMMIT_TABLE, nhưng ta đã tối ưu từ hơn 44 giây xuống chỉ còn 2 giây 68 (cải thiện 17 lần)

4. Các câu lệnh trong thực tế đều cần phải COMMIT, tần suất COMMIT có ảnh hưởng đến hiệu năng hay không?

Câu trả lời là có.

Chúng ta hãy cùng xem 2 vòng lặp Insert nhưng tần suất COMMIT khác nhau bên dưới đây

4.1. Phương án 1: Thực hiện COMMIT sau khi đã hoàn thành việc INSERT 1.000.000 bản ghi

SQL> set timing on
SQL> truncate table wecommit_table;

Table truncated.

Thực hiện Insert 1 triệu bản ghi vào bảng WECOMMIT_TABLES

SQL> begin
for i in 1 .. 1000000 loop
insert into WECOMMIT_TABLE values ( i, ‘WECOMMIT’||i);
end loop;
commit;
end;
/

PL/SQL procedure successfully completed.

Elapsed: 00:00:43.98

4.2. Phương án 2: Thực hiện COMMIT sau mỗi lệnh INSERT

Thực hiện truncate table trước khi Insert dữ liệu mới

SQL> truncate table WECOMMIT_TABLE;

Table truncated.

SQL> begin
for i in 1 .. 1000000 loop
insert into WECOMMIT_TABLE values ( i, ‘WECOMMIT’||i);
commit;
end loop;
end;
 /

PL/SQL procedure successfully completed.

Elapsed: 00:02:39.00

5. Tối ưu câu lệnh Insert dựa trên tối ưu Subquery

Trong nhiều trường hợp chúng ta sẽ cần thực hiện câu lệnh INSERT với kết quả trả ra của 1 câu lệnh SELECT.

Ví dụ xem xét câu lệnh sau

INSERT INTO emp_bk(EMPNO,ENAME)
SELECT id, last_name FROM emp where salary < 500

Chiến lược thực thi của câu lệnh này:

Tối ưu câu lệnh Insert trong Production

Câu lênh Insert ước lượng mất 7 phút 58s để thực hiện xong, trong đó thời gian lâu nhất chủ yếu là ở câu lệnh Subquery.

Chúng ta sẽ tối ưu câu lệnh Insert này bằng cách thực hiện tối ưu câu lệnh Subquery

SELECT id, last_name FROM emp where salary < 500

Trong trường hợp này chúng ta có thể áp dụng kỹ thuật tối ưu sử dụng Index:

create index idx_wecommit_salary on emp(salary)

Chiến lược thực thi sau khi tạo Index này như sau

Tối ưu câu lệnh Insert trong Production

Kết quả câu lệnh đã giảm từ 7ph 58s xuống còn 01s.

Bạn thấy bài viết này như thế nào?
0 reactions

Add new comment

Image CAPTCHA
Enter the characters shown in the image.

Related Articles

Mỗi kết nối cơ sở dữ liệu được định nghĩa trong một mảng, với tên kết nối là khóa của mảng

Eager Loading là một kỹ thuật tối ưu hóa truy vấn cơ sở dữ liệu trong Laravel, giúp tăng tốc độ truy vấn và giảm số lượng truy vấn cần thiết để lấy dữ liệu liên quan đến một bản ghi.

Để sử dụng Eager Loading với điều kiện trong Laravel, bạn có thể sử dụng phương thức whereHas hoặc orWhereHas trong Eloquent Builder.

E hiểu đơn giản vầy nha. auth() hay Auth trong laravel là những function global hay class, nó cũng chỉ là 1 thôi

Xin chào các bạn, tuần này mình sẽ viết một bài về cách xử lý Real Time(thời gian thực) với Laravel và Pusher