ตัวเลือก * ยังคงไม่มีอะไรใหญ่โตใน SQL Server 2012 หรือไม่


41

ย้อนกลับไปในสมัยของปีกลายถือเป็นเรื่องใหญ่ที่ไม่ต้องทำselect * from tableหรือselect count(*) from tableเพราะการแสดงที่โด่งดัง

ยังคงเป็นกรณีนี้ใน SQL Server รุ่นที่ใหม่กว่า (ฉันใช้ 2012 แต่ฉันเดาว่าคำถามจะใช้กับ 2008 - 2014)

แก้ไข:เนื่องจากผู้คนดูเหมือนจะประณามฉันเล็กน้อยที่นี่ฉันจึงมองสิ่งนี้จากมุมมองของมาตรฐาน / ทางวิชาการไม่ว่าจะเป็นสิ่งที่ "ถูกต้อง" (ซึ่งแน่นอนว่ามันไม่ใช่)

คำตอบ:


50

หากคุณSELECT COUNT(*) FROM TABLEที่ส่งคืนเพียงหนึ่งแถว (จำนวน) จะค่อนข้างเบาและเป็นวิธีรับตัวเลขนั้น

และSELECT *ไม่ได้เป็นไม่มีทางกายภาพในการที่จะถูกต้องตามกฎหมายและได้รับอนุญาต

อย่างไรก็ตามปัญหาSELECT *คือคุณสามารถทำให้เกิดการเคลื่อนย้ายข้อมูลมากขึ้น คุณทำงานในทุกคอลัมน์ในตาราง หากคุณSELECTมีเพียงไม่กี่คอลัมน์คุณอาจได้รับคำตอบจากดัชนีหรือดัชนีซึ่งจะลด I / O และผลกระทบต่อแคชเซิร์ฟเวอร์

ดังนั้นใช่แนะนำว่าเป็นการปฏิบัติทั่วไปเพราะเป็นการสิ้นเปลืองทรัพยากรของคุณ

ประโยชน์ที่แท้จริงเพียงอย่างเดียวของการSELECT *ไม่พิมพ์ชื่อคอลัมน์ทั้งหมด แต่จาก SSMS คุณสามารถใช้การลากแล้วปล่อยเพื่อรับชื่อคอลัมน์ในแบบสอบถามของคุณและลบชื่อที่คุณไม่ต้องการ

การเปรียบเทียบ:ถ้าใช้คนSELECT *เมื่อพวกเขาไม่จำเป็นต้องทุกคอลัมน์เขาจะยังใช้SELECTโดยไม่ต้องWHERE(หรือข้อ จำกัด อื่น ๆ ) เมื่อพวกเขาไม่จำเป็นต้องทุกแถว?


24

นอกเหนือจากคำตอบที่ได้รับจากผู้ให้บริการแล้วฉันรู้สึกว่ามันคุ้มค่าที่จะชี้ให้เห็นว่านักพัฒนามักขี้เกียจเมื่อทำงานกับ ORM สมัยใหม่เช่น Entity Framework ในขณะที่ DBA พยายามอย่างที่สุดที่จะหลีกเลี่ยงSELECT *ผู้พัฒนามักเขียนความหมายที่เทียบเท่ากันเช่นใน c # Linq:

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

ในสาระสำคัญนี้จะส่งผลในการต่อไปนี้:

SELECT * FROM MyTable WHERE FirstName = 'User'

นอกจากนี้ยังมีค่าใช้จ่ายเพิ่มเติมซึ่งยังไม่ได้ครอบคลุม นั่นคือทรัพยากรที่จำเป็นในการประมวลผลแต่ละคอลัมน์ในแต่ละแถวไปยังวัตถุที่เกี่ยวข้อง นอกจากนี้สำหรับทุกวัตถุที่เก็บไว้ในหน่วยความจำวัตถุนั้นจะต้องทำความสะอาด หากคุณเลือกเฉพาะคอลัมน์ที่คุณต้องการคุณสามารถบันทึกได้อย่างง่ายดายเกิน ram 100mb แม้ว่าจะไม่ได้เป็นจำนวนมาก แต่ก็มีผลสะสมของการรวบรวมขยะ ฯลฯ ซึ่งเป็นฝั่งไคลเอ็นต์ต้นทุน

ดังนั้นใช่อย่างน้อยที่สุดสำหรับฉันมันเป็นและจะเป็นใหญ่เสมอ นอกจากนี้เรายังต้องให้ความรู้เกี่ยวกับค่าใช้จ่าย "ซ่อนเร้น" ในการทำสิ่งนี้ด้วย

ภาคผนวก

นี่คือตัวอย่างของการดึงเฉพาะข้อมูลที่คุณต้องการตามที่ร้องขอในความคิดเห็น:

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

13

ผลการดำเนินงาน: แบบสอบถามกับ SELECT * อาจจะไม่เคยเป็นแบบสอบถามที่ครอบคลุม ( คำอธิบายพูดง่าย , Stack มากเกินคำอธิบาย )

การพิสูจน์ในอนาคต: แบบสอบถามของคุณอาจส่งคืนทั้งเจ็ดคอลัมน์วันนี้ แต่ถ้ามีใครเพิ่มห้าคอลัมน์ในปีถัดไปในปีถัดไปการสืบค้นของคุณจะคืนสิบสองคอลัมน์ให้สิ้นเปลือง IO และ CPU

การจัดทำดัชนี: หากคุณต้องการให้มุมมองและฟังก์ชั่นที่มีค่าเป็นตารางของคุณมีส่วนร่วมในการทำดัชนีใน SQL Server ดังนั้นมุมมองและฟังก์ชั่นเหล่านั้นจะต้องถูกสร้างขึ้นด้วย schemabinding ซึ่งห้ามการใช้ SELECT *

วิธีปฏิบัติที่ดีที่สุด : SELECT *ห้ามใช้ในรหัสการผลิต

สำหรับWHERE EXISTS ( SELECT 1 FROM … )คำถามย่อยฉันชอบ

แก้ไข : หากต้องการพูดถึงความคิดเห็นของ Craig Young ด้านล่างการใช้ "SELECT 1" ในแบบสอบถามย่อยไม่ใช่การเพิ่มประสิทธิภาพ "" - ดังนั้นฉันจึงสามารถยืนหน้าชั้นเรียนของฉันและพูดว่า "อย่าใช้ SELECT * โดยไม่มีข้อยกเว้น! "

เกี่ยวกับข้อยกเว้นเพียงอย่างเดียวที่ฉันสามารถนึกได้ก็คือลูกค้ากำลังทำการดำเนินการกับตารางสาระสำคัญและต้องการคอลัมน์ในปัจจุบันและอนาคตทั้งหมด

ฉันอาจยอมรับข้อยกเว้นที่เกี่ยวข้องกับ CTE และตารางที่ได้รับแม้ว่าฉันต้องการเห็นแผนการดำเนินการ

โปรดทราบว่าฉันพิจารณาCOUNT(*)ข้อยกเว้นสำหรับเรื่องนี้เพราะมันเป็นการใช้ไวยากรณ์ที่แตกต่างกันของ "*"


10

ใน SQL Server 2012 (หรือรุ่นใดก็ได้ตั้งแต่ปี 2005 ขึ้นไป) การใช้SELECT *...เป็นเพียงปัญหาด้านประสิทธิภาพที่เป็นไปได้ในคำสั่ง SELECT ระดับบนสุดของแบบสอบถาม

จึงไม่ได้เป็นปัญหาในการเข้าชม (*) ใน subqueries ในคำสั่งอยู่ใน CTEs และไม่ในSELECT COUNT(*)..ฯลฯ ฯลฯ หมายเหตุว่านี้อาจจะยังเป็นจริงสำหรับ Oracle และ DB2 และอาจ Postgres (ไม่แน่ใจ) แต่มีโอกาสมากที่จะยังคงมีปัญหาในหลายกรณีสำหรับ MySql

เพื่อให้เข้าใจว่าทำไม (และทำไมมันถึงยังคงเป็นปัญหาใน SELECT ระดับบนสุด) มันจะมีประโยชน์ที่จะเข้าใจว่าทำไมมันเคยมีปัญหาซึ่งเป็นเพราะการใช้SELECT *..หมายถึง " คืนคอลัมน์ทั้งหมด " โดยทั่วไปนี้จะกลับมาเป็นจำนวนมากข้อมูลได้มากขึ้นกว่าที่คุณต้องการจริงๆซึ่งเห็นได้ชัดว่าจะส่งผลให้จำนวนมากขึ้น IO ทั้งดิสก์และเครือข่าย

สิ่งที่ชัดเจนน้อยกว่าคือสิ่งนี้ยัง จำกัด ดัชนีและแบบสอบถามแผนใดที่ optimizer SQL สามารถใช้ได้เนื่องจากรู้ว่าท้ายที่สุดต้องส่งคืนคอลัมน์ข้อมูลทั้งหมด หากสามารถทราบล่วงหน้าว่าคุณต้องการคอลัมน์บางคอลัมน์เท่านั้นก็มักจะสามารถใช้แผนคิวรีที่มีประสิทธิภาพมากขึ้นโดยการใช้ประโยชน์จากดัชนีที่มีคอลัมน์เหล่านั้นเท่านั้น โชคดีที่มีวิธีการที่จะรู้ล่วงหน้าล่วงหน้าซึ่งคุณสามารถระบุคอลัมน์ที่คุณต้องการในรายการคอลัมน์ได้อย่างชัดเจน แต่เมื่อคุณใช้ "*" คุณกำลังละทิ้งสิ่งนี้เพราะ "มอบทุกอย่างให้ฉันฉันจะเข้าใจว่าฉันต้องการอะไร"

ใช่นอกจากนี้ยังมีการใช้ CPU และหน่วยความจำเพิ่มเติมในการประมวลผลทุกคอลัมน์ แต่เกือบจะเล็กน้อยเมื่อเทียบกับสองสิ่งนี้: ดิสก์พิเศษและแบนด์วิดท์เครือข่ายที่สำคัญที่จำเป็นสำหรับคอลัมน์ที่คุณไม่ต้องการและต้องใช้น้อย แผนแบบสอบถามที่ปรับให้เหมาะสมเพราะจะต้องรวมทุกคอลัมน์

แล้วมีอะไรเปลี่ยนแปลง? โดยพื้นฐานแล้วเครื่องมือเพิ่มประสิทธิภาพ SQL ได้รวมคุณสมบัติที่เรียกว่า "การเพิ่มประสิทธิภาพคอลัมน์" ซึ่งหมายความว่าตอนนี้พวกเขาสามารถค้นหาในคิวรีย่อยระดับล่างถ้าคุณจะใช้คอลัมน์ในระดับบนของแบบสอบถาม

ผลที่สุดของเรื่องนี้คือมันไม่สำคัญอีกต่อไปถ้าคุณใช้ 'SELECT * .. ' ในระดับล่าง / ภายในของแบบสอบถาม สิ่งที่สำคัญจริงๆคือสิ่งที่อยู่ในรายการคอลัมน์ของ SELECT ระดับบนสุด เว้นแต่คุณจะใช้SELECT *..ในด้านบนแล้วก็อีกครั้งต้องคิดว่าคุณต้องการทั้งหมดของคอลัมน์และอื่น ๆ ไม่สามารถเพิ่มประสิทธิภาพคอลัมน์จ้างได้อย่างมีประสิทธิภาพ

(* - โปรดทราบว่ามีปัญหาการเชื่อมโยงที่แตกต่างกันเล็กน้อยใน Views *ที่พวกเขาไม่ได้ลงทะเบียนการเปลี่ยนแปลงในรายการคอลัมน์เสมอเมื่อใช้ "*" มีวิธีอื่นในการแก้ไขปัญหานี้และไม่มีผลต่อประสิทธิภาพ)


5

มีเหตุผลเล็กน้อยอีกข้อหนึ่งที่ไม่ควรใช้SELECT *: หากคำสั่งของคอลัมน์คืนการเปลี่ยนแปลงแอปพลิเคชันของคุณจะพัง ... หากคุณโชคดี หากคุณไม่ใช่คุณจะพบข้อผิดพลาดเล็กน้อยที่ไม่สามารถตรวจจับได้เป็นเวลานาน คำสั่งของเขตข้อมูลในตารางเป็นรายละเอียดการดำเนินงานที่ไม่ควรได้รับการพิจารณาโดยการใช้งานเป็นเวลาเพียง SELECT *แต่มันมองเห็นได้แม้จะถูกถ้าคุณใช้


4
นี่ไม่เกี่ยวข้อง หากคุณเข้าถึงคอลัมน์ตามดัชนีคอลัมน์ในรหัสแอปพลิเคชันของคุณคุณควรจะมีแอปพลิเคชันที่ใช้งานไม่ได้ การเข้าถึงคอลัมน์ตามชื่อมักจะสร้างรหัสแอปพลิเคชันที่อ่านได้มากขึ้นและแทบไม่เคยเป็นปัญหาคอขวดของประสิทธิภาพเลย
Lie Ryan

3

มันได้รับอนุญาตทางร่างกายและมีปัญหาในการใช้select * from tableอย่างไรก็ตามมันเป็นความคิดที่ดี ทำไม?

ก่อนอื่นคุณจะพบว่าคุณส่งคืนคอลัมน์ที่คุณไม่ต้องการ (ทรัพยากรหนัก)

ประการที่สองมันจะใช้เวลานานในตารางขนาดใหญ่กว่าการตั้งชื่อคอลัมน์เพราะเมื่อคุณเลือก * คุณกำลังเลือกชื่อคอลัมน์จากฐานข้อมูลและพูดว่า "ให้ข้อมูลที่เกี่ยวข้องกับคอลัมน์ที่มีชื่อในรายการอื่นนี้ให้ฉัน ." แม้ว่าสิ่งนี้จะเป็นสิ่งที่รวดเร็วสำหรับโปรแกรมเมอร์ แต่ลองจินตนาการถึงการทำสิ่งนี้บนคอมพิวเตอร์ของธนาคารที่อาจมีการค้นหานับแสนครั้งในหนึ่งนาที

ประการที่สามการทำเช่นนี้จะทำให้ผู้พัฒนายากขึ้น คุณต้องพลิกกลับไปกลับมาจาก SSMS เป็น VS บ่อยแค่ไหนเพื่อรับชื่อคอลัมน์ทั้งหมด

ประการที่สี่มันเป็นสัญญาณของการเขียนโปรแกรมขี้เกียจและฉันไม่คิดว่านักพัฒนาจะต้องการชื่อเสียงนั้น


อาร์กิวเมนต์ที่สองของคุณในรูปแบบปัจจุบันนี้มีข้อผิดพลาดเล็กน้อย อันดับแรก RDBMS ทั้งหมดเก็บโครงร่างของตารางส่วนใหญ่เป็นเพราะรูปแบบจะถูกโหลดต่อไปที่ขั้นตอนการแยกวิเคราะห์แบบสอบถามเพื่อกำหนดคอลัมน์ที่มีอยู่หรือหายไปในตารางจากแบบสอบถาม ดังนั้นตัวแยกวิเคราะห์คิวรีได้ทำการค้นหารายการชื่อคอลัมน์ด้วยตนเองแล้วจึงแทนที่ * ด้วยรายการของคอลัมน์ทันที จากนั้นเอ็นจิ้น RDBMS ส่วนใหญ่จะพยายามแคชทุกอย่างเท่าที่จะทำได้ดังนั้นหากคุณออกตาราง SELECT * จากตารางดังนั้นแบบสอบถามที่รวบรวมจะถูกแคชดังนั้นการแยกวิเคราะห์จะไม่เกิดขึ้นทุกครั้ง และนักพัฒนาจะขี้เกียจ :-)
บอร์ Garami

เกี่ยวกับอาร์กิวเมนต์ที่สองของคุณนี่เป็นความเข้าใจผิดที่พบบ่อย - ปัญหาเกี่ยวกับ SELECT * ไม่ใช่การค้นหาข้อมูลเมตาเนื่องจากถ้าคุณตั้งชื่อคอลัมน์ SQL Server ยังคงต้องตรวจสอบชื่อของพวกเขาตรวจสอบชนิดข้อมูล ฯลฯ
Aaron Bertrand

@Gabor หนึ่งในปัญหาของ SELECT * จะเกิดขึ้นเมื่อคุณใส่เข้าไปในมุมมอง หากคุณเปลี่ยนสคีมาพื้นฐานมุมมองอาจสับสน - ตอนนี้มันมีแนวคิดที่แตกต่างกันของสคีมาของตาราง (ของมันเอง) กว่าของตาราง ฉันพูดคุยเกี่ยวกับเรื่องนี้ที่นี่
Aaron Bertrand

3

มันอาจเป็นปัญหาหากคุณใส่Select * ...รหัสในโปรแกรมเพราะตามที่ระบุไว้ก่อนหน้านี้ฐานข้อมูลอาจเปลี่ยนแปลงตลอดเวลาและมีคอลัมน์มากกว่าสิ่งที่คุณคาดไว้เมื่อคุณเขียนแบบสอบถาม สิ่งนี้อาจนำไปสู่ความล้มเหลวของโปรแกรม (กรณีที่ดีที่สุด) หรือโปรแกรมอาจดำเนินต่อไปอย่างสนุกสนานและทำให้ข้อมูลเสียหายเนื่องจากมองไปที่ค่าฟิลด์ที่ไม่ได้เขียนขึ้นเพื่อจัดการ กล่าวโดยย่อรหัสการผลิตควรระบุเขตข้อมูลที่จะส่งคืนในSELECTเสมอ

ต้องบอกว่าฉันมีปัญหาน้อยลงเมื่อSelect *เป็นส่วนหนึ่งของEXISTSประโยคเนื่องจากสิ่งที่จะถูกส่งกลับไปยังโปรแกรมเป็นบูลีนบ่งชี้ความสำเร็จหรือความล้มเหลวของการเลือก คนอื่นอาจไม่เห็นด้วยกับขาตั้งนี้และฉันเคารพความคิดเห็นของพวกเขาในเรื่องนั้น อาจมีประสิทธิภาพน้อยกว่ารหัสเล็กน้อยSelect *ในการเขียนรหัส 'เลือก 1' ในEXISTSข้อ แต่ฉันไม่คิดว่าจะมีอันตรายจากข้อมูลเสียหายทั้งสองทาง


จริง ๆ แล้วใช่ฉันตั้งใจจะอ้างอิงข้อ EXISTS ความผิดพลาดของฉัน.
Mark Ross

2

มีคำตอบมากมายว่าทำไมselect *มันผิดดังนั้นฉันจะอธิบายเมื่อฉันรู้สึกว่ามันถูกหรืออย่างน้อยก็โอเค

1) ใน EXISTS เนื้อหาของส่วน SELECT ของแบบสอบถามจะถูกละเว้นดังนั้นคุณสามารถเขียนได้SELECT 1/0และจะไม่มีข้อผิดพลาด EXISTSเพียงตรวจสอบว่าข้อมูลบางอย่างจะส่งคืนและส่งคืนบูลีนตามนั้น

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

2) สิ่งนี้อาจเริ่มต้นเปลวไฟ แต่ฉันชอบใช้select *ในทริกเกอร์ตารางประวัติของฉัน โดยselect *จะป้องกันไม่ให้ตารางหลักสร้างคอลัมน์ใหม่โดยไม่ต้องเพิ่มคอลัมน์ในตารางประวัติเช่นกันโดยจะเกิดข้อผิดพลาดทันทีเมื่อแทรก / อัพเดท / ลบลงในตารางหลัก สิ่งนี้ทำให้มีหลายครั้งที่นักพัฒนาเพิ่มคอลัมน์และลืมเพิ่มลงในตารางประวัติ


3
ฉันยังชอบSELECT 1เพราะเห็นได้ชัดว่ามันแจ้งให้ผู้ดูแลโค้ดในอนาคตทราบถึงความตั้งใจของคุณ มันไม่ได้เป็นข้อกำหนดแต่ถ้าฉันเห็น... WHERE EXISTS (SELECT 1 ...)ว่ามันค่อนข้างชัดเจนประกาศตัวเองว่าเป็นการทดสอบความจริง
swasheck

1
คน @zlatanMany ใช้ขึ้นอยู่กับตำนานว่าประสิทธิภาพการทำงานจะดีกว่าSELECT 1 SELECT *อย่างไรก็ตามตัวเลือกทั้งสองเป็นที่ยอมรับอย่างสมบูรณ์ ไม่มีความแตกต่างในประสิทธิภาพเนื่องจากวิธีที่ optimser จัดการ EXISTS หรือความแตกต่างในการอ่านเพราะคำว่า "EXISTS" ที่ประกาศการทดสอบความจริงอย่างชัดเจน
ไม่แยแส

ในจุด # 2 ฉันเข้าใจเหตุผลของคุณ แต่ยังมีความเสี่ยง ให้ฉัน 'ระบายสีสถานการณ์สำหรับคุณ' ... นักพัฒนาเพิ่มColumn8ลงในตารางหลักลืมตารางประวัติ ผู้พัฒนาเขียนโค้ดจำนวนมากที่ติดตั้งไว้ที่คอลัมน์ 8 จากนั้นเขาเพิ่มColumn9ลงในตารางหลัก เวลานี้การจดจำเพื่อเพิ่มลงในประวัติ ต่อมาเมื่อการทดสอบเขาตระหนักว่าเขาลืมที่จะเพิ่มColumn9ในประวัติศาสตร์ (ขอบคุณเทคนิคการตรวจสอบข้อผิดพลาดของคุณ) และเพิ่มมันทันที ทริกเกอร์ตอนนี้ดูเหมือนว่าจะทำงานได้ แต่ข้อมูลในคอลัมน์ 8 และ 9 ถูกผสมกันในประวัติศาสตร์ : S
ไม่แยแส

ต่อ ... ประเด็นก็คือสถานการณ์สมมติ 'การปรุง' ข้างต้นเป็นเพียงหนึ่งในหลาย ๆ สิ่งที่อาจส่งผลให้เกิดเคล็ดลับการตรวจจับข้อผิดพลาดของคุณทำให้คุณล้มเหลวและทำให้สิ่งต่าง ๆ แย่ลง โดยทั่วไปคุณต้องมีเทคนิคที่ดีกว่า หนึ่งที่ไม่ขึ้นอยู่กับทริกเกอร์ของคุณทำให้สมมติฐานเกี่ยวกับลำดับของคอลัมน์ในตารางที่คุณเลือกจาก คำแนะนำ: - รีวิวรหัสส่วนตัวพร้อมรายการตรวจสอบข้อผิดพลาดทั่วไปของคุณ - ความคิดเห็นรหัสเพื่อน - เทคนิคสำรองสำหรับการติดตามประวัติ (โดยส่วนตัวแล้วฉันพิจารณากลไกที่ทริกเกอร์เพื่อให้ทำปฏิกิริยาแทนที่จะเป็นเชิงรุกและมีแนวโน้มที่จะเกิดข้อผิดพลาด)
ไม่แยแส

@ CraigYoung นั่นเป็นความเป็นไปได้ แต่ฉันจะเค้นใครสักคนถ้าพวกเขาทำอย่างนั้น นั่นไม่ใช่ข้อผิดพลาดที่คุณสามารถทำได้ง่ายๆ
UnhandledExcepSean
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.