Chi phí chung của việc cập nhật tất cả các cột là gì, ngay cả những cột chưa thay đổi [đã đóng]


17

Khi nói đến việc cập nhật một hàng, nhiều công cụ ORM đưa ra câu lệnh CẬP NHẬT đặt mọi cột được liên kết với thực thể cụ thể đó .

Ưu điểm là bạn có thể dễ dàng bó các câu lệnh cập nhật vì UPDATEcâu lệnh giống nhau cho dù bạn thay đổi thuộc tính thực thể nào. Hơn nữa, bạn thậm chí có thể sử dụng bộ đệm ẩn câu lệnh phía máy chủ và phía máy khách.

Vì vậy, nếu tôi tải một thực thể và chỉ đặt một thuộc tính:

Post post = entityManager.find(Post.class, 1L);
post.setScore(12);

Tất cả các cột sẽ được thay đổi:

UPDATE post
SET    score = 12,
       title = 'High-Performance Java Persistence'
WHERE  id = 1

Bây giờ, giả sử rằng chúng ta cũng có một chỉ mục trên thuộc titletính, DB không nên nhận ra rằng giá trị đã không thay đổi?

Trong bài viết này , Markus Winand nói:

Bản cập nhật trên tất cả các cột hiển thị cùng một mẫu mà chúng ta đã quan sát trong các phần trước: thời gian phản hồi tăng lên với mỗi chỉ số bổ sung.

Tôi tự hỏi tại sao chi phí này vì cơ sở dữ liệu tải trang dữ liệu liên quan từ đĩa vào bộ nhớ và do đó nó có thể tìm ra liệu một giá trị cột có cần phải thay đổi hay không.

Ngay cả đối với các chỉ mục, nó cũng không cân bằng lại bất cứ điều gì vì các giá trị chỉ mục không thay đổi đối với các cột không thay đổi, nhưng chúng đã được đưa vào CẬP NHẬT.

Có phải các chỉ mục B + Tree được liên kết với các cột không thay đổi dự phòng cũng cần phải được điều hướng, chỉ để cơ sở dữ liệu nhận ra rằng giá trị lá vẫn giống nhau?

Tất nhiên, một số công cụ ORM cho phép bạn CẬP NHẬT chỉ các thuộc tính đã thay đổi:

UPDATE post
SET    score = 12,
WHERE  id = 1

Nhưng loại CẬP NHẬT này có thể không phải lúc nào cũng được hưởng lợi từ các bản cập nhật hàng loạt hoặc bộ đệm ẩn câu lệnh khi các thuộc tính khác nhau được thay đổi cho các hàng khác nhau.


1
Nếu cơ sở dữ liệu là PostgreSQL (hoặc một số người khác sử dụng MVCC ), UPDATEthì thực tế tương đương với dấu DELETE+ INSERT(vì bạn thực sự tạo ra một chữ V mới của hàng). Chi phí hoạt động cao và tăng theo số lượng chỉ mục , đặc biệt nếu nhiều cột bao gồm chúng được cập nhật thực sự và cây (hoặc bất cứ thứ gì) được sử dụng để thể hiện chỉ mục cần một thay đổi đáng kể. Đây không phải là số lượng cột được cập nhật những gì có liên quan, nhưng liệu bạn có cập nhật một phần cột của chỉ mục hay không.
joanolo

@joanolo Điều này chỉ cần đúng với việc triển khai MVCC của postgres. MySQL, Oracle (và những người khác) thực hiện cập nhật tại chỗ và di chuyển các cột đã thay đổi vào không gian của UNDO.
Morgan Tocker

2
Tôi nên chỉ ra rằng một ORM tốt sẽ theo dõi cột nào cần cập nhật và tối ưu hóa câu lệnh được gửi đến cơ sở dữ liệu. Nó có liên quan, nếu chỉ đối với lượng dữ liệu được truyền tới DB, đặc biệt nếu một số cột là văn bản dài hoặc BLOB .
joanolo

1
Câu hỏi thảo luận về vấn đề này cho SQL Server dba.stackexchange.com/q/114360/3690
Martin Smith

2
DBMS nào bạn đang sử dụng?
a_horse_with_no_name

Câu trả lời:


12

Tôi biết bạn chủ yếu quan tâm UPDATEvà chủ yếu về hiệu suất, nhưng với tư cách là người duy trì "ORM" đồng nghiệp, hãy để tôi cung cấp cho bạn một góc nhìn khác về vấn đề phân biệt giữa các giá trị "thay đổi" , "null""mặc định" , đó là ba điều khác nhau trong SQL, nhưng có thể chỉ có một điều trong Java và trong hầu hết các ORM:

Dịch lý do của bạn để INSERTbáo cáo

Các đối số của bạn có lợi cho tính linh hoạt và khả năng lưu trữ câu lệnh giữ đúng theo cách tương tự đối với các INSERTcâu lệnh như chúng làm cho các UPDATEcâu lệnh. Nhưng trong trường hợp các INSERTcâu lệnh, bỏ qua một cột trong câu lệnh có một ngữ nghĩa khác với trong UPDATE. Nó có nghĩa là để áp dụng DEFAULT. Hai cái sau tương đương về mặt ngữ nghĩa:

INSERT INTO t (a, b)    VALUES (1, 2);
INSERT INTO t (a, b, c) VALUES (1, 2, DEFAULT);

Điều này không đúng với UPDATE, trong đó hai cái đầu tiên tương đương về mặt ngữ nghĩa và cái thứ ba có ý nghĩa hoàn toàn khác:

-- These are the same
UPDATE t SET a = 1, b = 2;
UPDATE t SET a = 1, b = 2, c = c;

-- This is different!
UPDATE t SET a = 1, b = 2, c = DEFAULT;

Hầu hết các API máy khách cơ sở dữ liệu, bao gồm JDBC và do đó, JPA, không cho phép liên kết một DEFAULTbiểu thức với biến liên kết - chủ yếu là do các máy chủ không cho phép điều này. Nếu bạn muốn sử dụng lại cùng một câu lệnh SQL cho các lý do khả năng lưu trữ hàng loạt và khả năng lưu lại câu lệnh đã nói ở trên, bạn sẽ sử dụng câu lệnh sau trong cả hai trường hợp (giả sử (a, b, c)là tất cả các cột trong t):

INSERT INTO t (a, b, c) VALUES (?, ?, ?);

Và vì ckhông được đặt, nên bạn có thể liên kết Java nullvới biến liên kết thứ ba, vì nhiều ORM cũng không thể phân biệt giữa NULLDEFAULT( jOOQ , ví dụ như là một ngoại lệ ở đây). Họ chỉ nhìn thấy Java nullvà không biết điều này có nghĩa NULL(như trong giá trị không xác định) hoặc DEFAULT(như trong giá trị chưa được khởi tạo).

Trong nhiều trường hợp, sự khác biệt này không thành vấn đề, nhưng trong trường hợp cột c của bạn đang sử dụng bất kỳ tính năng nào sau đây, thì tuyên bố đơn giản là sai :

  • Nó có một DEFAULTđiều khoản
  • Nó có thể được tạo ra bởi một kích hoạt

Quay lại UPDATEbáo cáo

Mặc dù những điều trên là đúng với tất cả các cơ sở dữ liệu, tôi có thể đảm bảo với bạn rằng vấn đề kích hoạt cũng đúng với cơ sở dữ liệu Oracle. Hãy xem xét SQL sau:

CREATE TABLE x (a INT PRIMARY KEY, b INT, c INT, d INT);

INSERT INTO x VALUES (1, 1, 1, 1);

CREATE OR REPLACE TRIGGER t
  BEFORE UPDATE OF c, d
  ON x
BEGIN
  IF updating('c') THEN
    dbms_output.put_line('Updating c');
  END IF;
  IF updating('d') THEN
    dbms_output.put_line('Updating d');
  END IF;
END;
/

SET SERVEROUTPUT ON
UPDATE x SET b = 1 WHERE a = 1;
UPDATE x SET c = 1 WHERE a = 1;
UPDATE x SET d = 1 WHERE a = 1;
UPDATE x SET b = 1, c = 1, d = 1 WHERE a = 1;

Khi bạn chạy ở trên, bạn sẽ thấy đầu ra sau:

table X created.
1 rows inserted.
TRIGGER T compiled
1 rows updated.
1 rows updated.
Updating c

1 rows updated.
Updating d

1 rows updated.
Updating c
Updating d

Như bạn có thể thấy, câu lệnh luôn cập nhật tất cả các cột sẽ luôn kích hoạt trình kích hoạt cho tất cả các cột, trong khi các câu lệnh chỉ cập nhật các cột đã thay đổi sẽ chỉ kích hoạt những trình kích hoạt đang lắng nghe những thay đổi cụ thể đó.

Nói cách khác:

Hành vi hiện tại của Hibernate mà bạn mô tả là không đầy đủ và thậm chí có thể bị coi là sai khi có trình kích hoạt (và có thể là các công cụ khác).

Cá nhân tôi nghĩ rằng đối số tối ưu hóa bộ đệm truy vấn của bạn được đánh giá cao trong trường hợp SQL động. Chắc chắn, sẽ có thêm một vài truy vấn trong bộ đệm như vậy và một số công việc phân tích cú pháp sẽ được thực hiện, nhưng điều này thường không phải là vấn đề đối với các UPDATEcâu lệnh động , ít hơn nhiều so với SELECT.

Batching chắc chắn là một vấn đề, nhưng theo tôi, một bản cập nhật duy nhất không nên được chuẩn hóa để cập nhật tất cả các cột chỉ vì có một khả năng nhỏ là câu lệnh có thể được bó. Rất có thể, ORM có thể thu thập các lô con của các câu lệnh giống hệt nhau liên tiếp và các đợt thay vì "toàn bộ lô" (trong trường hợp ORM thậm chí có khả năng theo dõi sự khác biệt giữa "thay đổi" , "null""mặc định"


Các DEFAULTtrường hợp sử dụng có thể được giải quyết bằng @DynamicInsert. Tình huống TRIGGER cũng có thể được xử lý bằng các kiểm tra như WHEN (NEW.b <> OLD.b)hoặc chỉ chuyển sang @DynamicUpdate.
Vlad Mihalcea

Vâng, mọi thứ có thể được giải quyết, nhưng ban đầu bạn đã hỏi về hiệu suất và cách giải quyết của bạn còn tăng thêm chi phí.
Lukas Eder

Tôi nghĩ Morgan nói điều đó tốt nhất: nó phức tạp .
Vlad Mihalcea

Tôi nghĩ nó khá đơn giản. Từ góc độ khung công tác, có nhiều đối số ủng hộ mặc định cho SQL động. Từ góc độ người dùng, vâng, nó phức tạp.
Lukas Eder

9

Tôi nghĩ câu trả lời là - nó phức tạp . Tôi đã cố gắng viết một bằng chứng nhanh bằng cách sử dụng một longtextcột trong MySQL, nhưng câu trả lời là không có kết luận. Bằng chứng đầu tiên:

# in advance:
set global max_allowed_packet=1024*1024*1024;

CREATE TABLE `t2` (
  `a` int(11) NOT NULL AUTO_INCREMENT,
  `b` char(255) NOT NULL,
  `c` LONGTEXT,
  PRIMARY KEY (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

mysql> insert into t2 (a, b, c) values (null, 'b', REPEAT('c', 1024*1024*1024));
Query OK, 1 row affected (38.81 sec)

mysql> UPDATE t2 SET b='new'; # fast
Query OK, 1 row affected (6.73 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql>  UPDATE t2 SET b='new'; # fast
Query OK, 0 rows affected (2.87 sec)
Rows matched: 1  Changed: 0  Warnings: 0

mysql> UPDATE t2 SET b='new'; # fast
Query OK, 0 rows affected (2.61 sec)
Rows matched: 1  Changed: 0  Warnings: 0

mysql> UPDATE t2 SET c= REPEAT('d', 1024*1024*1024); # slow (changed value)
Query OK, 1 row affected (22.38 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> UPDATE t2 SET c= REPEAT('d', 1024*1024*1024); # still slow (no change)
Query OK, 0 rows affected (14.06 sec)
Rows matched: 1  Changed: 0  Warnings: 0

Vì vậy, có một sự khác biệt nhỏ về thời gian giữa giá trị chậm + thay đổi và chậm + không thay đổi giá trị. Vì vậy, tôi quyết định xem xét một số liệu khác, đó là các trang được viết:

mysql> show global status like 'innodb_pages_written';
+----------------------+--------+
| Variable_name        | Value  |
+----------------------+--------+
| Innodb_pages_written | 198656 |
+----------------------+--------+
1 row in set (0.00 sec)

mysql> show global status like 'innodb_pages_written';
+----------------------+--------+
| Variable_name        | Value  |
+----------------------+--------+
| Innodb_pages_written | 198775 | <-- 119 pages changed in a "no change"
+----------------------+--------+
1 row in set (0.01 sec)

mysql> show global status like 'innodb_pages_written';
+----------------------+--------+
| Variable_name        | Value  |
+----------------------+--------+
| Innodb_pages_written | 322494 | <-- 123719 pages changed in a "change"!
+----------------------+--------+
1 row in set (0.00 sec)

Vì vậy, có vẻ như thời gian tăng lên vì phải có một so sánh để xác nhận rằng giá trị đó chưa được sửa đổi, trong trường hợp văn bản dài 1G mất nhiều thời gian (vì nó được chia thành nhiều trang). Nhưng bản sửa đổi dường như không đảo qua nhật ký làm lại.

Tôi nghi ngờ rằng nếu các giá trị là các cột thông thường nằm trong trang thì việc so sánh chỉ thêm một chút chi phí. Và giả sử áp dụng tối ưu hóa tương tự, đây là những điều không có trong bản cập nhật.

Câu trả lời dài hơn

Tôi thực sự nghĩ rằng ORM không nên loại bỏ các cột đã được sửa đổi ( nhưng không thay đổi ), vì tối ưu hóa này có tác dụng phụ lạ.

Hãy xem xét những điều sau đây trong mã giả:

# Initial Data does not make sense
# should be either "Harvey Dent" or "Two Face"

id: 1, firstname: "Two Face", lastname: "Dent"

session1.start
session2.start

session1.firstname = "Two"
session1.lastname = "Face"
session1.save

session2.firstname = "Harvey"
session2.lastname = "Dent"
session2.save

Kết quả nếu ORM là "Tối ưu hóa" sửa đổi mà không thay đổi:

id: 1, firstname: "Harvey", lastname: "Face"

Kết quả nếu ORM gửi tất cả các sửa đổi đến máy chủ:

id: 1, firstname: "Harvey", lastname: "Dent"

Trường hợp thử nghiệm ở đây dựa vào repeatable-readsự cô lập (mặc định của MySQL), nhưng một cửa sổ thời gian cũng tồn tại để read-committedcách ly trong đó việc đọc session2 xảy ra trước khi cam kết session1.

Nói một cách khác: tối ưu hóa chỉ an toàn nếu bạn phát hành một hàng SELECT .. FOR UPDATEđể đọc các hàng theo sau bởi một UPDATE. SELECT .. FOR UPDATEkhông sử dụng MVCC và luôn đọc phiên bản mới nhất của các hàng.


Chỉnh sửa: Đảm bảo bộ dữ liệu trường hợp kiểm tra là 100% trong bộ nhớ. Điều chỉnh kết quả thời gian.


Cảm ơn đã giải thích. Đó cũng là trực giác của tôi. Tôi nghĩ rằng DB sẽ kiểm tra cả hàng trong trang dữ liệu và tất cả các chỉ mục liên quan. Nếu cột rất lớn hoặc có hàng tấn chỉ số liên quan, chi phí có thể trở nên đáng chú ý. Nhưng đối với hầu hết các tình huống, khi sử dụng các loại cột nhỏ gọn và chỉ số càng nhiều chỉ số cần thiết, tôi đoán rằng chi phí có thể ít hơn là không được hưởng lợi từ bộ đệm ẩn câu lệnh hoặc có cơ hội bó câu lệnh thấp hơn.
Vlad Mihalcea

1
@VladMihalcea hãy cẩn thận rằng câu trả lời là về MySQL. Các kết luận có thể không giống nhau trong các DBMS khác nhau.
ypercubeᵀᴹ

@ypercube Tôi biết điều đó. Tất cả phụ thuộc vào RDBMS.
Vlad Mihalcea
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.