Có phải chọn * vẫn là không lớn trên SQL Server 2012 không?


41

Quay trở lại những ngày cuối năm, nó được coi là một việc không nên làm select * from tablehoặc select count(*) from tablevì thành tích đạt được.

Đây có phải là trường hợp trong các phiên bản sau của SQL Server (tôi đang sử dụng 2012, nhưng tôi đoán câu hỏi sẽ áp dụng cho 2008 - 2014)?

Chỉnh sửa: Vì mọi người dường như trượt tôi một chút ở đây, tôi đang xem xét điều này từ quan điểm chuẩn / học thuật, không phải đó là điều "đúng" phải làm (tất nhiên là không phải)

Câu trả lời:


50

Nếu bạn SELECT COUNT(*) FROM TABLEchỉ trả về một hàng (số đếm), thì tương đối nhẹ và là cách để lấy mốc đó.

SELECT *không phải là không có thể, trong đó nó là hợp pháp và được phép.

Tuy nhiên, vấn đề với SELECT *là bạn có thể gây ra sự di chuyển dữ liệu nhiều hơn. Bạn hoạt động trên mỗi cột trong bảng. Nếu bạn SELECTchỉ bao gồm một vài cột, bạn có thể nhận được câu trả lời của mình từ một chỉ mục hoặc chỉ mục, điều này làm giảm I / O và cả tác động lên bộ đệm của máy chủ.

Vì vậy, , nó được khuyến khích chống lại như một thông lệ chung vì nó gây lãng phí tài nguyên của bạn.

Lợi ích thực sự duy nhất của việc SELECT *không gõ tất cả các tên cột. Nhưng từ SSMS, bạn có thể sử dụng kéo và thả để lấy tên cột trong truy vấn của mình và xóa những tên bạn không cần.

Một sự tương tự: Nếu ai đó sử dụng SELECT *khi họ không cần mỗi cột, họ cũng sẽ sử dụng SELECTmà không có WHERE(hoặc một số mệnh đề giới hạn khác) khi họ không cần mỗi hàng?


24

Ngoài câu trả lời đã có nhà cung cấp, tôi cảm thấy rằng đáng để chỉ ra rằng các nhà phát triển thường quá lười biếng khi làm việc với ORM hiện đại, chẳng hạn như Entity Framework. Trong khi DBA cố gắng hết sức để tránh SELECT *, các nhà phát triển thường viết tương đương về mặt ngữ nghĩa, ví dụ, trong c # Linq:

var someVariable = db.MyTable.Where(entity => entity.FirstName == "User").ToList();

Về bản chất, điều này sẽ dẫn đến kết quả như sau:

SELECT * FROM MyTable WHERE FirstName = 'User'

Ngoài ra còn có một chi phí bổ sung chưa được bảo hiểm. Đó là các tài nguyên cần thiết để xử lý mỗi cột trong mỗi hàng cho đối tượng có liên quan. Hơn nữa, đối với mọi đối tượng được giữ trong bộ nhớ, đối tượng đó phải được dọn sạch. Nếu bạn chỉ chọn các cột mà bạn cần, bạn có thể dễ dàng tiết kiệm hơn 100mb ram. Mặc dù không phải là một số lượng lớn, nhưng hiệu ứng tích lũy của việc thu gom rác, v.v ... đó là phía khách hàng chi phí.

Vì vậy, có, đối với tôi ít nhất, nó luôn luôn là một không lớn. Chúng ta cũng cần phải được giáo dục về các chi phí "ẩn" để làm điều này nhiều hơn nữa.

Phụ lục

Dưới đây là mẫu chỉ lấy dữ liệu bạn cần theo yêu cầu trong các nhận xét:

var someVariable = db.MyTable.Where(entity => entity.FirstName == "User")
                             .Select(entity => new { entity.FirstName, entity.LastNight });

13

Hiệu suất: Một truy vấn với SELECT * có thể sẽ không bao giờ là truy vấn bao phủ ( Giải thích cuộc nói chuyện đơn giản , giải thích chồng chéo ).

Kiểm chứng trong tương lai: Truy vấn của bạn có thể trả về tất cả bảy cột ngày hôm nay nhưng nếu ai đó thêm năm cột trong năm tới thì trong một năm, truy vấn của bạn sẽ trả lại mười hai cột, gây lãng phí IO và CPU.

Lập chỉ mục: Nếu bạn muốn các khung nhìn và các hàm có giá trị bảng của mình tham gia lập chỉ mục trong SQL Server thì các khung nhìn và các hàm đó phải được tạo bằng lược đồ, cấm sử dụng CHỌN *.

Thực hành tốt nhất : không bao giờ sử dụng SELECT *trong mã sản xuất.

Đối với các truy vấn con, tôi thích WHERE EXISTS ( SELECT 1 FROM … ).

Chỉnh sửa : Để giải quyết nhận xét của Craig Young bên dưới, sử dụng "CHỌN 1" trong truy vấn con không phải là "tối ưu hóa" - vì vậy tôi có thể đứng trước lớp và nói "không sử dụng CHỌN *, không có ngoại lệ! "

Về ngoại lệ duy nhất tôi có thể nghĩ đến là nơi khách hàng đang thực hiện một số loại hoạt động bảng trụ và yêu cầu tất cả các cột hiện tại và tương lai.

Tôi có thể chấp nhận một ngoại lệ liên quan đến CTE và các bảng dẫn xuất, mặc dù tôi muốn xem các kế hoạch thực hiện.

Lưu ý rằng tôi xem xét COUNT(*)một ngoại lệ cho điều này bởi vì đó là cách sử dụng cú pháp khác nhau của "*".


10

Trong SQL Server 2012, (hoặc bất kỳ phiên bản nào từ năm 2005 trở lên), việc sử dụng SELECT *...chỉ là vấn đề hiệu năng có thể xảy ra trong câu lệnh CHỌN cấp cao nhất của truy vấn.

Vì vậy, đó không phải là vấn đề trong Lượt xem (*), trong các truy vấn con, trong các mệnh đề EXIST, CTE, cũng như SELECT COUNT(*)..v.v. Lưu ý, điều này có lẽ cũng đúng với Oracle và DB2 và có thể là PostGres (không chắc chắn) , nhưng rất có khả năng nó vẫn là một vấn đề trong rất nhiều trường hợp đối với MySql.

Để hiểu lý do tại sao (và tại sao nó vẫn có thể là một vấn đề trong CHỌN cấp cao nhất), thật hữu ích để hiểu lý do tại sao nó từng là một vấn đề, đó là vì sử dụng SELECT *..có nghĩa là " trả về TẤT CẢ các cột ". Nói chung, điều này sẽ trả về nhiều dữ liệu hơn bạn thực sự muốn, điều này rõ ràng có thể dẫn đến nhiều IO hơn, cả đĩa và mạng.

Điều ít rõ ràng hơn là điều này cũng hạn chế những chỉ mục và kế hoạch truy vấn mà trình tối ưu hóa SQL có thể sử dụng, bởi vì nó biết rằng cuối cùng nó phải trả về tất cả các cột dữ liệu. Nếu bạn có thể biết trước rằng bạn chỉ muốn một số cột nhất định, thì nó thường có thể sử dụng các gói truy vấn hiệu quả hơn bằng cách tận dụng các chỉ mục chỉ có các cột đó. May mắn thay, có một cách để nó biết trước điều này, đó là cách để bạn chỉ định rõ ràng các cột bạn muốn trong danh sách cột. Nhưng khi bạn sử dụng "*", bạn sẽ từ bỏ điều này vì "chỉ cần cho tôi mọi thứ, tôi sẽ tìm ra thứ tôi cần."

Có, cũng có sử dụng CPU và bộ nhớ bổ sung để xử lý mỗi cột, nhưng hầu như luôn luôn là nhỏ so với hai điều sau: băng thông mạng và đĩa phụ đáng kể cần thiết cho các cột mà bạn không cần và phải sử dụng ít hơn kế hoạch truy vấn được tối ưu hóa bởi vì nó phải bao gồm mọi cột.

Vậy điều gì đã thay đổi? Về cơ bản, Trình tối ưu hóa SQL đã kết hợp thành công một tính năng gọi là "Tối ưu hóa cột" có nghĩa là, giờ đây chúng có thể tìm ra trong các truy vấn phụ cấp thấp hơn nếu bạn thực sự sẽ sử dụng một cột ở cấp cao hơn của truy vấn.

Kết quả cuối cùng là điều này không còn quan trọng nữa nếu bạn sử dụng 'CHỌN * ..' ở cấp độ thấp hơn / bên trong của truy vấn. Thay vào đó, điều thực sự quan trọng là những gì trong danh sách cột của CHỌN cấp cao nhất. Trừ khi bạn sử dụng SELECT *..ở trên cùng, sau đó một lần nữa, phải cho rằng bạn muốn TẤT CẢ các cột và do đó không thể sử dụng tối ưu hóa cột một cách hiệu quả.

(* - lưu ý rằng có một vấn đề ràng buộc nhỏ, khác trong Chế độ xem *khi chúng không luôn luôn đăng ký thay đổi trong danh sách cột khi "*" được sử dụng. Có nhiều cách khác để giải quyết vấn đề này và nó không ảnh hưởng đến hiệu suất.)


5

Có thêm một lý do nhỏ để không sử dụng SELECT *: nếu thứ tự các cột trả về thay đổi, ứng dụng của bạn sẽ bị hỏng ... nếu bạn may mắn. Nếu bạn không, bạn sẽ có một lỗi tinh vi có thể không bị phát hiện trong một thời gian dài. Thứ tự của các trường trong bảng là một chi tiết triển khai không bao giờ được các ứng dụng xem xét, vì lần duy nhất nó có thể nhìn thấy được là nếu bạn sử dụng a SELECT *.


4
Điều này là không liên quan. Nếu bạn đang truy cập các cột theo chỉ mục cột trong mã ứng dụng của mình, thì bạn xứng đáng có một ứng dụng bị hỏng. Truy cập các cột theo tên luôn tạo ra mã ứng dụng dễ đọc hơn nhiều và nó gần như không bao giờ là nút cổ chai hiệu năng.
Lie Ryan

3

Nó được phép sử dụng về mặt vật lý và có vấn đề select * from table, tuy nhiên, đó là một ý tưởng tồi. Tại sao?

Trước hết, bạn sẽ thấy rằng bạn đang trả về các cột mà bạn không cần (tài nguyên nặng).

Thứ hai, sẽ mất nhiều thời gian hơn trên một bảng lớn hơn so với việc đặt tên các cột vì khi bạn chọn *, thực tế bạn đang chọn tên cột từ cơ sở dữ liệu và nói "hãy cho tôi dữ liệu được liên kết với các cột có tên trong danh sách khác này . " Mặc dù điều này nhanh chóng đối với lập trình viên, hãy tưởng tượng thực hiện việc tìm kiếm này trên máy tính của một ngân hàng có thể có hàng trăm ngàn lượt tra cứu trong một phút.

Thứ ba, làm điều này thực sự gây khó khăn hơn cho nhà phát triển. Bạn có thường xuyên phải lật qua lại từ SSMS sang VS để lấy tất cả a của các tên cột không?

Thứ tư, đó là một dấu hiệu của lập trình lười biếng và tôi không nghĩ rằng bất kỳ nhà phát triển nào cũng muốn danh tiếng đó.


Đối số thứ hai của bạn trong hình thức hiện tại này có một số sai lầm nhỏ. Đầu tiên, tất cả RDBMS lưu trữ lược đồ của các bảng, chủ yếu là vì lược đồ sẽ được tải bằng mọi cách ở giai đoạn phân tích truy vấn để xác định cột nào tồn tại hoặc thiếu trong bảng từ truy vấn. Vì vậy, trình phân tích cú pháp truy vấn đã tự truy vấn danh sách tên cột và thay thế ngay lập tức * bằng danh sách các cột. Sau đó, hầu hết các công cụ RDBMS đều cố gắng lưu trữ tất cả những gì có thể, vì vậy nếu bạn phát hành bảng CHỌN * TỪ, thì truy vấn đã biên dịch sẽ được lưu vào bộ đệm để việc phân tích cú pháp không xảy ra mỗi lần. Và các nhà phát triển lười biếng :-)
Gabor Garami

Về đối số thứ hai của bạn, đây là một quan niệm sai lầm phổ biến - vấn đề với SELECT * không phải là tra cứu siêu dữ liệu, vì nếu bạn đặt tên cho các cột, SQL Server vẫn phải xác thực tên của chúng, kiểm tra các loại dữ liệu, v.v.
Aaron Bertrand

@Gabor Một trong những vấn đề với CHỌN * xảy ra khi bạn đặt nó trong chế độ xem. Nếu bạn thay đổi lược đồ cơ bản, khung nhìn có thể bị lẫn lộn - giờ đây nó có một khái niệm khác về lược đồ của bảng (của chính nó) so với chính bảng. Tôi nói về điều này ở đây .
Aaron Bertrand

3

Nó có thể là một vấn đề nếu bạn đặt Select * ...mã trong một chương trình, bởi vì, như đã chỉ ra trước đó, cơ sở dữ liệu có thể thay đổi theo thời gian và có nhiều cột hơn những gì bạn mong đợi khi bạn viết truy vấn. Điều này có thể dẫn đến lỗi chương trình (trường hợp tốt nhất) hoặc chương trình có thể đi theo hướng vui vẻ và làm hỏng một số dữ liệu vì nó nhìn vào các giá trị trường mà nó không được viết để xử lý. Nói tóm lại, mã sản xuất LUÔN LUÔN chỉ định các trường được trả về trong SELECT.

Có nói rằng, tôi có ít vấn đề hơn khi Select *là một phần của một EXISTSmệnh đề, vì tất cả những gì sẽ được trả lại cho chương trình là một boolean chỉ ra sự thành công hay thất bại của lựa chọn. Những người khác có thể không đồng ý với quan điểm này và tôi tôn trọng ý kiến ​​của họ về điều đó. Mã có thể kém hiệu quả hơn một chút Select *so với mã 'Chọn 1' trong một EXISTSmệnh đề, nhưng tôi không nghĩ rằng có bất kỳ nguy cơ tham nhũng dữ liệu nào.


Trên thực tế, vâng, tôi đã có ý tham khảo mệnh đề EXISTS. Lỗi của tôi.
Đánh dấu Ross

2

Rất nhiều câu trả lời tại sao select *là sai, vì vậy tôi sẽ trình bày khi tôi cảm thấy nó đúng hoặc ít nhất là OK.

1) Trong EXISTS, nội dung của phần CHỌN của truy vấn bị bỏ qua, do đó bạn thậm chí có thể viết SELECT 1/0và nó sẽ không bị lỗi. EXISTSchỉ cần xác minh rằng một số dữ liệu sẽ trả về và trả về một boolean dựa trên đó.

IF EXISTS(
    SELECT * FROM Table WHERE X=@Y
)

2) Điều này có thể bắt đầu một cơn bão lửa, nhưng tôi thích sử dụng select *trong trình kích hoạt bảng lịch sử của mình. Bởi select *, nó ngăn bảng chính lấy một cột mới mà không thêm cột vào bảng lịch sử cũng như nó bị lỗi ngay lập tức khi chèn / cập nhật / xóa vào bảng chính. Điều này đã ngăn chặn nhiều lần các nhà phát triển thêm cột và quên thêm nó vào bảng lịch sử.


3
Tôi vẫn thích SELECT 1bởi vì nó rõ ràng nhất thông báo cho những người duy trì mã trong tương lai về ý định của bạn. Đó không phải là một yêu cầu , nhưng nếu tôi thấy ... WHERE EXISTS (SELECT 1 ...)nó khá rõ ràng tự tuyên bố đó là một thử nghiệm sự thật.
swasheck

1
@zlatanMany mọi người sử dụng SELECT 1dựa trên một huyền thoại rằng hiệu suất sẽ tốt hơn SELECT *. Tuy nhiên, cả hai lựa chọn đều hoàn toàn chấp nhận được. Không có sự khác biệt về hiệu suất do cách bộ xử lý quang học xử lý EXISTS. Cũng không có sự khác biệt về khả năng đọc vì từ "EXISTS" thông báo rõ ràng một bài kiểm tra sự thật.
vỡ mộng

Ở điểm số 2, tôi hiểu lý lẽ của bạn, nhưng vẫn có những rủi ro. Hãy để tôi 'vẽ một kịch bản cho bạn' ... Nhà phát triển thêm Column8vào bảng chính mà quên bảng lịch sử. Nhà phát triển viết một bó mã được chuyển sang Cột 8. Sau đó, anh ta thêm Column9vào bảng chính; lần này nhớ để thêm vào lịch sử. Sau này khi kiểm tra anh ta nhận ra rằng anh ta quên thêm Column9vào lịch sử (nhờ vào kỹ thuật phát hiện lỗi của bạn) và nhanh chóng thêm nó vào. Bây giờ kích hoạt có vẻ hoạt động, nhưng dữ liệu trong cột 8 & 9 được trộn lẫn trong lịch sử. : S
vỡ mộng

tiếp ... Vấn đề là kịch bản 'được pha chế' ở trên chỉ là một trong nhiều tình huống có thể dẫn đến thủ thuật phát hiện lỗi của bạn làm bạn thất bại và thực sự làm mọi thứ tồi tệ hơn. Về cơ bản bạn cần một kỹ thuật tốt hơn. Một cái không dựa vào trình kích hoạt của bạn đưa ra các giả định về thứ tự của các cột trong bảng mà bạn chọn. Gợi ý: - Đánh giá mã cá nhân với danh sách kiểm tra các lỗi phổ biến của bạn. - Đánh giá mã ngang hàng. - Kỹ thuật thay thế để theo dõi lịch sử (cá nhân tôi coi các cơ chế dựa trên kích hoạt là phản ứng thay vì chủ động, và do đó dễ bị lỗi).
vỡ mộng

@CraigYoung Đó là một khả năng. Nhưng tôi sẽ tiết lưu ai đó nếu họ làm điều đó. Đó không phải là một sai lầm mà bạn có thể dễ dàng mắc phải
UnhandledEx805Sean
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.