Tony Hoare, người thường được coi là người phát minh ra tham chiếu NULL, giờ đây gọi đó là một sai lầm hàng tỷ đô la mà hầu hết các ngôn ngữ hiện nay đều đang “mắc phải”, bao gồm cả SQL.
Trích dẫn Tony (từ bài viết trên Wikipedia của anh ấy):
Tôi gọi đó là sai lầm hàng tỷ đô la của mình. Đó là phát minh của tham chiếu null vào năm 1965. Vào thời điểm đó, tôi đang thiết kế hệ thống kiểu toàn diện đầu tiên cho các tham chiếu bằng ngôn ngữ hướng đối tượng (ALGOL W). Mục tiêu của tôi là đảm bảo rằng tất cả việc sử dụng các tham chiếu phải an toàn tuyệt đối, với việc kiểm tra được thực hiện tự động bởi trình biên dịch. Nhưng tôi không thể cưỡng lại sự cám dỗ để đưa vào một tham chiếu rỗng, đơn giản vì nó rất dễ thực hiện. Điều này đã dẫn đến vô số lỗi, lỗ hổng bảo mật và sự cố hệ thống, có lẽ đã gây ra đau đớn và thiệt hại hàng tỷ đô la trong bốn mươi năm qua.Điều thú vị ở đây là Tony đã bị cám dỗ để triển khai tài liệu tham khảo đó vì nó rất dễ thực hiện. Nhưng tại sao anh ta thậm chí cần một tài liệu tham khảo như vậy?
Các ý nghĩa khác nhau của NULL
Trong một thế giới hoàn hảo, chúng ta sẽ không cần NULL. Mỗi người đều có họ và tên. Mỗi người đều có ngày sinh, công việc, v.v. Hay là họ?
Thật không may, họ không.
Không phải tất cả các quốc gia đều sử dụng khái niệm họ và tên.
Không phải tất cả mọi người đều có việc làm. Hoặc đôi khi, chúng tôi không biết công việc của họ. Hoặc chúng tôi không quan tâm.
Đây là nơi mà NULL cực kỳ hữu ích. NULL có thể lập mô hình tất cả các trạng thái này mà chúng tôi không thực sự muốn lập mô hình. NULL có thể là:
- Giá trị “không xác định” , tức là, giá trị chưa được xác định (có thể vì lý do kỹ thuật) nhưng cũng có thể được xác định sau. Hãy nghĩ về một người mà chúng ta muốn thêm vào cơ sở dữ liệu để sử dụng nó trong các bảng khác. Ở giai đoạn sau, chúng tôi sẽ thêm công việc của người đó.
- Giá trị "không xác định" , tức là, giá trị mà chúng ta không biết (và có thể không bao giờ biết). Có lẽ chúng tôi không còn có thể hỏi người này hoặc người thân của họ về ngày sinh của họ nữa - thông tin sẽ vĩnh viễn bị mất. Nhưng chúng tôi vẫn muốn mô hình hóa người đó, vì vậy chúng tôi sử dụng NULL với nghĩa là KHÔNG CÓ (đó là ý nghĩa thực sự của nó trong SQL, như chúng ta sẽ thấy ở phần sau).
- Giá trị "tùy chọn" tức là giá trị không cần xác định. Lưu ý rằng giá trị "tùy chọn" cũng xuất hiện trong trường hợp của OUTER JOIN, khi kết nối bên ngoài không tạo ra bất kỳ giá trị nào ở một phía của mối quan hệ. Hoặc cũng có thể khi sử dụng BỘ NHÓM, trong đó các kết hợp khác nhau của các cột NHÓM THEO được kết hợp (hoặc để trống).
- Giá trị “đã xóa” hoặc “đã tránh” , tức là giá trị mà chúng tôi không muốn chỉ định. Có lẽ chúng tôi thường đăng ký tình trạng hôn nhân của một người như được thực hiện ở một số khu vực pháp lý, nhưng không phải ở những khu vực khác, nơi không hợp pháp để đăng ký bất kỳ dữ liệu cá nhân nào thuộc loại này. Do đó, chúng tôi không muốn biết giá trị này trong một số trường hợp.
- Giá trị "đặc biệt" trong ngữ cảnh nhất định , tức là, giá trị mà chúng tôi không thể lập mô hình khác trong phạm vi giá trị có thể. Điều này thường được thực hiện khi làm việc với phạm vi ngày. Giả sử công việc của một người bị giới hạn bởi hai ngày và nếu người đó hiện đang làm việc ở vị trí đó, chúng tôi sẽ sử dụng NULL để nói rằng khoảng thời gian không bị giới hạn vào cuối phạm vi ngày.
- NULL "ngẫu nhiên" , tức là giá trị NULL chỉ là NULL vì các nhà phát triển không chú ý. Trong trường hợp không có ràng buộc NOT NULL rõ ràng, hầu hết các cơ sở dữ liệu đều giả định các cột là giá trị rỗng. Và một khi các cột không có giá trị, các nhà phát triển có thể “vô tình” đặt giá trị NULL vào các hàng của họ, nơi họ thậm chí không có ý định làm như vậy.
Như chúng ta đã thấy ở trên, đây chỉ là một số lựa chọn trong số 50 sắc thái của NULL .
Ví dụ sau hiển thị nhiều ý nghĩa khác nhau của NULL trong một ví dụ SQL cụ thể:
CREATE TABLE company ( id int NOT NULL, name text NOT NULL, CONSTRAINT company_pk PRIMARY KEY (id) ); CREATE TABLE job ( person_id int NOT NULL, start_date date NOT NULL, -- If end_date IS NULL, the “special value” of an unbounded -- interval is encoded end_date date NULL, description text NOT NULL, -- A job doesn’t have to be done at a company. It is “optional”. company_id int NULL, CONSTRAINT job_pk PRIMARY KEY (person_id,start_date), CONSTRAINT job_company FOREIGN KEY (company_id) REFERENCES company (id) ); CREATE TABLE person ( id int NOT NULL, first_name text NOT NULL, -- Some people need to be created in the database before we -- know their last_names. It is “undefined” last_name text NULL, -- We may not know the date_of_birth. It is “unknown” date_of_birth date NULL, -- In some situations, we must not define any marital_status. -- It is “deleted” marital_status int NULL, CONSTRAINT person_pk PRIMARY KEY (id), CONSTRAINT job_person FOREIGN KEY (person_id) REFERENCES person (id) );
Mọi người luôn tranh cãi về việc không có giá trị
Khi NULL là một giá trị hữu ích như vậy, tại sao mọi người lại tiếp tục chỉ trích nó?
Tất cả các trường hợp sử dụng trước đây cho NULL (và các trường hợp khác) được hiển thị trong bài nói chuyện thú vị, gần đây của C.J. Date về “Vấn đề thiếu thông tin” (xem video trên YouTube).
SQL hiện đại có thể làm được nhiều điều tuyệt vời mà rất ít nhà phát triển các ngôn ngữ đa năng như Java, C #, PHP không biết. Tôi sẽ chỉ cho bạn một ví dụ sâu hơn.
Theo một cách nào đó, C.J. Date đồng ý với Tony Hoare rằng (ab) sử dụng NULL cho tất cả các loại “thông tin bị thiếu” khác nhau này là một lựa chọn rất tồi.
Ví dụ:trong thiết bị điện tử, các kỹ thuật tương tự được áp dụng cho những thứ như 1, 0, “xung đột”, “chưa được gán”, “không xác định”, “không quan tâm”, “trở kháng cao”. Mặc dù vậy, hãy lưu ý rằng trong điện tử, các giá trị đặc biệt khác nhau được sử dụng cho những thứ này, thay vì một giá trị NULL đặc biệt duy nhất . Điều này có thực sự tốt hơn không? Các lập trình viên JavaScript cảm thấy thế nào về sự phân biệt giữa các giá trị “giả” khác nhau, như “null”, “không xác định”, “0”, “NaN”, chuỗi rỗng ‘’? Điều này có thực sự tốt hơn không?
Nói về số 0:Khi chúng ta rời khỏi không gian SQL một chút và đi vào toán học, chúng ta sẽ thấy rằng các nền văn hóa cổ đại như người La Mã hoặc Hy Lạp đều gặp phải vấn đề tương tự với số 0. Trên thực tế, họ thậm chí không có bất kỳ cách nào để biểu thị số 0 không giống như các nền văn hóa khác như có thể thấy trong bài viết trên Wikipedia về số 0. Trích dẫn từ bài báo:
Các ghi chép cho thấy người Hy Lạp cổ đại dường như không chắc chắn về trạng thái của số 0 như một con số. Họ tự hỏi mình, “Làm thế nào mà không có gì có thể là một cái gì đó?”, Dẫn đến triết học và, vào thời Trung cổ, các lập luận tôn giáo về bản chất và sự tồn tại của số không và chân không.Như chúng ta có thể thấy, "lập luận tôn giáo" rõ ràng mở rộng sang khoa học máy tính và phần mềm, nơi chúng ta vẫn không biết chắc chắn phải làm gì khi không có giá trị.
Quay lại thực tế:NULL trong SQL
Trong khi mọi người (bao gồm cả các học giả) vẫn chưa đồng ý về thực tế liệu chúng ta có cần bất kỳ mã hóa nào cho "không xác định", "không xác định", "tùy chọn", "đã xóa", "đặc biệt" hay không, hãy để chúng tôi quay trở lại thực tế và những phần xấu về SQL's NULL.
Một điều thường bị lãng quên khi xử lý SQL’s NULL là nó chính thức triển khai trường hợp UNKNOWN, là một giá trị đặc biệt là một phần của cái gọi là logic ba giá trị và nó làm như vậy, không nhất quán, ví dụ:trong trường hợp hoạt động của UNION hoặc INTERSECT.
Nếu chúng ta quay lại mô hình của mình:
Ví dụ:nếu chúng tôi muốn tìm tất cả những người chưa đăng ký kết hôn, theo trực giác, chúng tôi muốn viết tuyên bố sau:
SELECT * FROM person WHERE marital_status != 'married'
Rất tiếc, do logic ba giá trị và SQL’s NULL, truy vấn trên sẽ không trả về những giá trị không có bất kỳ marital_status rõ ràng nào. Do đó, chúng tôi sẽ cần viết một vị từ bổ sung, rõ ràng:
SELECT * FROM person WHERE marital_status != 'married' OR marital_status IS NULL
Hoặc, chúng tôi ép giá trị thành một số giá trị KHÔNG ĐẦY ĐỦ trước khi so sánh nó
SELECT * FROM person WHERE COALESCE(marital_status, 'null') != 'married'
Ba lôgic có giá trị thật khó. Và đó không phải là vấn đề duy nhất với NULL trong SQL. Dưới đây là những nhược điểm khác của việc sử dụng NULL:
- Chỉ có một NULL, khi chúng tôi thực sự muốn mã hóa một số giá trị "không có" hoặc "đặc biệt" khác nhau. Phạm vi của các giá trị đặc biệt hữu ích phụ thuộc nhiều vào miền và kiểu dữ liệu được sử dụng. Tuy nhiên, kiến thức miền luôn luôn được yêu cầu để diễn giải chính xác ý nghĩa của cột có thể vô hiệu và các truy vấn phải được thiết kế cẩn thận để tránh trả về kết quả sai, như chúng ta đã thấy ở trên.
- Một lần nữa, lôgic ba giá trị rất khó đúng. Trong khi ví dụ trên vẫn còn khá đơn giản, bạn nghĩ truy vấn sau sẽ mang lại kết quả gì?
SELECT * FROM person WHERE marital_status NOT IN ('married', NULL)
Chính xác. Nó sẽ không mang lại bất cứ điều gì, như được giải thích trong bài viết này ở đây. Tóm lại, truy vấn trên giống với truy vấn dưới đây:
SELECT * FROM person WHERE marital_status != 'married' AND marital_status != NULL -- This is always NULL / UNKNOWN
-
Cơ sở dữ liệu Oracle xử lý NULL và chuỗi rỗng '' giống nhau. Điều này rất phức tạp vì bạn sẽ không nhận ra ngay lý do tại sao truy vấn sau luôn trả về kết quả trống:
SELECT * FROM person WHERE marital_status NOT IN ('married', '')
-
Oracle (một lần nữa) không đặt giá trị NULL trong chỉ mục. Đây là nguồn gốc của nhiều vấn đề hiệu suất khó chịu, ví dụ:khi bạn đang sử dụng cột có thể vô hiệu trong vị ngữ KHÔNG VÀO, chẳng hạn như:
SELECT * FROM person WHERE marital_status NOT IN ( SELECT some_nullable_column FROM some_table )
Với Oracle, việc chống tham gia ở trên sẽ dẫn đến việc quét toàn bộ bảng, bất kể bạn có chỉ mục trên some_nullable_column hay không. Do logic ba giá trị và bởi vì Oracle không đặt NULL trong chỉ mục, công cụ sẽ cần phải đánh dấu vào bảng và kiểm tra mọi giá trị chỉ để đảm bảo không có ít nhất một giá trị NULL trong tập hợp, điều này sẽ làm cho toàn bộ vị từ UNKNOWN.
Kết luận
Chúng tôi vẫn chưa giải quyết được vấn đề NULL ở hầu hết các ngôn ngữ và nền tảng. Mặc dù tôi khẳng định rằng NULL KHÔNG phải là sai lầm hàng tỷ đô la mà Tony Hoare cố gắng xin lỗi, nhưng NULL chắc chắn cũng không hoàn hảo.
Nếu bạn muốn an toàn với thiết kế cơ sở dữ liệu của mình, hãy tránh NULL bằng mọi giá, trừ khi bạn thực sự cần một trong những giá trị đặc biệt đó để mã hóa bằng NULL. Hãy nhớ rằng các giá trị này là:“không xác định”, “không xác định”, “tùy chọn”, “đã xóa” và “đặc biệt”, v.v.: 50 sắc thái của NULL . Nếu bạn không ở trong trường hợp như vậy, hãy luôn mặc định thêm ràng buộc NOT NULL vào mọi cột trong cơ sở dữ liệu của bạn. Thiết kế của bạn sẽ gọn gàng hơn nhiều và hiệu suất của bạn tốt hơn nhiều.
Nếu chỉ NOT NULL là từ khóa mặc định trong DDL và NULLABLE từ khóa cần được đặt rõ ràng…
Bạn có những trải nghiệm và kinh nghiệm nào với NULL? Theo bạn thì một SQL tốt hơn sẽ hoạt động như thế nào?
Lukas Eder là người sáng lập và Giám đốc điều hành của Data Geekery GmbH, đặt tại Zurich, Thụy Sĩ. Data Geekery đã bán các sản phẩm và dịch vụ cơ sở dữ liệu xung quanh Java và SQL từ năm 2013.
Kể từ khi học Thạc sĩ tại EPFL vào năm 2006, anh ấy đã bị cuốn hút bởi sự tương tác của Java và SQL. Hầu hết kinh nghiệm này anh ấy có được trong lĩnh vực Ngân hàng điện tử của Thụy Sĩ thông qua các biến thể khác nhau (JDBC, Hibernate, chủ yếu là với Oracle). Anh ấy rất vui khi được chia sẻ kiến thức này tại các hội nghị khác nhau, các JUG, các bài thuyết trình nội bộ và blog của công ty anh ấy.