คำถาม "ฉันควรใช้ ORM ใด" คือการกำหนดเป้าหมายปลายสุดของภูเขาน้ำแข็งขนาดใหญ่เมื่อพูดถึงกลยุทธ์การเข้าถึงข้อมูลโดยรวมและการเพิ่มประสิทธิภาพในแอปพลิเคชันขนาดใหญ่
การออกแบบและการบำรุงรักษาฐานข้อมูล
นี่คือขอบเขตที่กว้างที่สุดปัจจัยที่สำคัญที่สุดเพียงอย่างเดียวของทรูพุตของแอพพลิเคชั่นหรือเว็บไซต์ที่ขับเคลื่อนด้วยข้อมูลและมักจะถูกละเลยโดยโปรแกรมเมอร์
หากคุณไม่ได้ใช้เทคนิคการทำให้เป็นมาตรฐานที่เหมาะสมเว็บไซต์ของคุณจะถูกลงโทษ หากคุณไม่มีคีย์หลักเกือบทุกคำค้นหาจะดูช้า หากคุณใช้รูปแบบการต่อต้านที่รู้จักกันดีเช่นการใช้ตารางสำหรับคู่คีย์ - ค่า (AKA Entity-Attribute-Value) โดยไม่มีเหตุผลคุณจะระเบิดจำนวนการอ่านและเขียนทางกายภาพ
หากคุณไม่ได้ใช้ประโยชน์จากคุณสมบัติที่ฐานข้อมูลมอบให้คุณเช่นการบีบอัดหน้าการFILESTREAM
จัดเก็บ (สำหรับข้อมูลไบนารี) SPARSE
คอลัมน์hierarchyid
สำหรับลำดับชั้นและอื่น ๆ (ตัวอย่าง SQL Server ทั้งหมด) คุณจะไม่เห็นตำแหน่งใกล้เคียง การแสดงที่คุณจะได้เห็น
คุณควรเริ่มกังวลเกี่ยวกับกลยุทธ์การเข้าถึงข้อมูลของคุณหลังจากที่คุณออกแบบฐานข้อมูลและเชื่อมั่นในตัวเองว่าเป็นสิ่งที่ดีอย่างที่ควรจะเป็นในช่วงเวลานั้น
กระตือรือร้นกับการโหลดขี้เกียจ
ORM ส่วนใหญ่ใช้เทคนิคที่เรียกว่าlazy loadingสำหรับความสัมพันธ์ซึ่งหมายความว่าโดยค่าเริ่มต้นมันจะโหลดเอนทิตีหนึ่ง (แถวของตาราง) ในแต่ละครั้งและทำการเดินทางไปยังฐานข้อมูลทุกครั้งที่ต้องการโหลดหนึ่งหรือหลายอัน คีย์) แถว
นี่ไม่ใช่สิ่งที่ดีหรือไม่ดีมันค่อนข้างขึ้นอยู่กับสิ่งที่จะทำจริงกับข้อมูลและเท่าไหร่ที่คุณรู้ล่วงหน้า บางครั้งการโหลดสันหลังยาวเป็นสิ่งที่ถูกต้อง ตัวอย่างเช่น NHibernate อาจตัดสินใจที่จะไม่ค้นหาสิ่งใดเลยและเพียงสร้างพร็อกซีสำหรับรหัสเฉพาะ หากทั้งหมดที่คุณต้องการคือรหัสตัวเองเหตุใดจึงควรถามเพิ่มเติม ในทางตรงกันข้ามถ้าคุณกำลังพยายามที่จะพิมพ์ต้นไม้ขององค์ประกอบเดียวทุกคนในลำดับชั้นที่ 3 ระดับขี้เกียจโหลดกลายเป็น O (N²) การดำเนินงานซึ่งเป็นอย่างมากที่ไม่ดีสำหรับการทำงาน
ประโยชน์อย่างหนึ่งที่น่าสนใจในการใช้ "pure SQL" (เช่น raw แบบสอบถาม ADO.NET / กระบวนงานที่เก็บไว้) คือโดยพื้นฐานแล้วมันบังคับให้คุณคิดอย่างแม่นยำว่าข้อมูลใดที่จำเป็นต้องแสดงหน้าจอหรือหน้าใด ๆ คุณลักษณะ ORM และการโหลดแบบสันหลังยาวไม่ได้ป้องกันคุณจากการทำเช่นนี้ แต่จะทำให้คุณมีโอกาสที่จะ ... ดีขี้เกียจและไม่ได้ตั้งใจกระจายจำนวนข้อความค้นหาที่คุณเรียกใช้โดยไม่ตั้งใจ ดังนั้นคุณต้องเข้าใจถึงคุณสมบัติการโหลด ORM ของคุณและต้องระมัดระวังเกี่ยวกับจำนวนข้อความค้นหาที่คุณส่งไปยังเซิร์ฟเวอร์สำหรับคำขอหน้าใด ๆ
เก็บเอาไว้
ORM หลัก ๆ ทั้งหมดรักษาแคชระดับแรก AKA "รหัสประจำตัวแคช" ซึ่งหมายความว่าหากคุณร้องขอเอนทิตีเดียวกันสองครั้งด้วย ID ของมันก็ไม่จำเป็นต้องมีรอบที่สองและ (ถ้าคุณออกแบบฐานข้อมูลของคุณอย่างถูกต้อง ) ช่วยให้คุณสามารถใช้การทำงานพร้อมกันในแง่ดี
แคช L1 นั้นค่อนข้างทึบใน L2S และ EF คุณต้องเชื่อว่ามันใช้งานได้ NHibernate มีความชัดเจนมากขึ้นเกี่ยวกับเรื่องนี้ ( Get
/ Load
vs. Query
/ QueryOver
) อย่างไรก็ตามตราบใดที่คุณพยายามสืบค้นด้วย ID ให้มากที่สุดคุณควรจะทำได้ที่นี่ ผู้คนจำนวนมากลืมเกี่ยวกับแคช L1 และค้นหาเอนทิตีเดียวกันซ้ำ ๆ ซ้ำ ๆ โดยสิ่งอื่นที่ไม่ใช่ ID (เช่นฟิลด์ค้นหา) หากคุณต้องการทำสิ่งนี้คุณควรบันทึก ID หรือแม้แต่เอนทิตีทั้งหมดสำหรับการค้นหาในอนาคต
นอกจากนี้ยังมีแคชระดับ 2 ("แคชแบบสอบถาม") NHibernate มีสิ่งนี้ในตัว Linq ไปยัง SQL และ Entity Framework มีการรวบรวมแบบสอบถามซึ่งสามารถช่วยลดแอปเซิร์ฟเวอร์โหลดได้ค่อนข้างน้อยด้วยการรวบรวมการแสดงออกของแบบสอบถามเอง แต่ไม่ได้แคชข้อมูล Microsoft ดูเหมือนจะพิจารณาเรื่องนี้เกี่ยวกับแอปพลิเคชั่นมากกว่าความกังวลเกี่ยวกับการเข้าถึงข้อมูลและนี่เป็นจุดอ่อนที่สำคัญของทั้ง L2S และ EF ไม่จำเป็นต้องบอกว่าเป็นจุดอ่อนของ SQL "raw" เพื่อให้ได้ประสิทธิภาพที่ดีจริง ๆ กับ ORM อื่น ๆ ที่ไม่ใช่ NHibernate คุณต้องติดตั้งหน้าแคชของคุณเอง
นอกจากนี้ยังมี "ส่วนขยาย" แคช L2 สำหรับ EF4 ซึ่งก็โอเคแต่ไม่สามารถทดแทนแคชในระดับแอปพลิเคชันได้อย่างแท้จริง
จำนวนคำค้นหา
ฐานข้อมูลเชิงสัมพันธ์ขึ้นอยู่กับชุดของข้อมูล พวกเขากำลังดีจริงๆที่ผลิตขนาดใหญ่ปริมาณของข้อมูลในระยะเวลาสั้น ๆ แต่พวกเขาไม่ได้อยู่ใกล้เป็นดีในแง่ของการค้นหาความล่าช้าเพราะมีจำนวนหนึ่งของค่าใช้จ่ายที่เกี่ยวข้องในทุกคำสั่ง แอปที่ออกแบบมาอย่างดีควรเล่นกับจุดแข็งของ DBMS นี้และพยายามลดจำนวนการค้นหาและลดจำนวนข้อมูลในแต่ละรายการให้ได้มากที่สุด
ตอนนี้ฉันไม่ได้กำลังสอบถามฐานข้อมูลทั้งหมดเมื่อคุณต้องการเพียงหนึ่งแถว สิ่งที่ฉันพูดคือถ้าคุณต้องการCustomer
, Address
, Phone
, CreditCard
และOrder
แถวทั้งหมดในเวลาเดียวกันเพื่อให้บริการหน้าเดียวแล้วคุณควรจะถามสำหรับพวกเขาทั้งหมดในเวลาเดียวกันไม่ได้ดำเนินการค้นหาแต่ละครั้งแยกต่างหาก บางครั้งมันแย่กว่านั้นคุณจะเห็นรหัสที่ถามCustomer
เร็กคอร์ดเดียวกัน5 ครั้งติดต่อกันก่อนที่จะได้รับId
แล้วName
จากEmailAddress
นั้นก็จากนั้น ... มันไม่มีประสิทธิภาพอย่างน่าขัน
แม้ว่าคุณจะต้องเรียกใช้คิวรีหลายชุดที่ทำงานทั้งหมดในชุดข้อมูลที่แตกต่างกันอย่างสมบูรณ์ แต่ก็มักจะมีประสิทธิภาพมากกว่าในการส่งทั้งหมดไปยังฐานข้อมูลในรูปแบบ "สคริปต์" เดียวและส่งคืนชุดผลลัพธ์หลายชุด เป็นค่าใช้จ่ายที่คุณกังวลไม่ใช่ปริมาณข้อมูลทั้งหมด
สิ่งนี้อาจฟังดูเป็นเรื่องสามัญ แต่บ่อยครั้งที่ง่ายต่อการติดตามการสืบค้นทั้งหมดที่ถูกดำเนินการในส่วนต่าง ๆ ของแอปพลิเคชัน ผู้ให้บริการสมาชิกของคุณทำการสอบถามผู้ใช้ / ตารางบทบาท, การดำเนินการส่วนหัวของคุณจะสอบถามตะกร้าสินค้า, การกระทำเมนูของคุณจะค้นหาตารางแผนที่เว็บไซต์, การกระทำแถบด้านข้างของคุณสอบถามรายการผลิตภัณฑ์ที่โดดเด่นแล้วอาจแบ่งหน้าของคุณออกเป็น สอบถามประวัติการสั่งซื้อ, ดูล่าสุด, หมวดหมู่และตารางสินค้าคงคลังแยกจากกันและก่อนที่คุณจะรู้คุณกำลังดำเนินการค้นหา 20 ครั้งก่อนที่คุณจะสามารถเริ่มแสดงหน้าได้ มันทำลายประสิทธิภาพอย่างเต็มที่
กรอบบางอย่าง - และฉันคิดว่าส่วนใหญ่ของ NHibernate ที่นี่ - มีความฉลาดอย่างเหลือเชื่อเกี่ยวกับเรื่องนี้และให้คุณใช้สิ่งที่เรียกว่าฟิวเจอร์สที่รวบรวมคำถามทั้งหมดและพยายามที่จะดำเนินการทั้งหมดในครั้งเดียวในนาทีสุดท้าย AFAIK คุณเป็นเจ้าของด้วยตัวคุณเองถ้าคุณต้องการทำสิ่งนี้ด้วยเทคโนโลยีใด ๆ ของ Microsoft คุณต้องสร้างมันเป็นตรรกะแอปพลิเคชันของคุณ
การทำดัชนีเพรดิเคตและเส้นโครง
อย่างน้อย 50% ของ devs ที่ฉันพูดถึงและแม้แต่ DBA บางคนก็ดูเหมือนจะมีปัญหากับแนวคิดของการครอบคลุมดัชนี พวกเขาคิดว่า " Customer.Name
เอาล่ะคอลัมน์ถูกจัดทำดัชนีดังนั้นการค้นหาทุกครั้งที่ฉันทำกับชื่อควรเร็ว" ยกเว้นว่าจะไม่ทำงานเช่นนั้นเว้นแต่Name
ดัชนีจะครอบคลุมคอลัมน์เฉพาะที่คุณกำลังค้นหา ใน SQL Server เสร็จแล้วINCLUDE
ในCREATE INDEX
คำสั่ง
หากคุณใช้SELECT *
ทุกที่ - และนั่นคือสิ่งที่ ORM ทุกอย่างจะทำมากไปกว่านั้นเว้นแต่คุณจะระบุเป็นอย่างอื่นอย่างชัดเจนโดยใช้การฉายภาพแล้ว DBMS อาจเลือกที่จะละเว้นดัชนีของคุณอย่างสมบูรณ์เพราะมีคอลัมน์ที่ไม่ครอบคลุม การฉายภาพหมายถึงยกตัวอย่างเช่นแทนที่จะทำสิ่งนี้:
from c in db.Customers where c.Name == "John Doe" select c
คุณทำสิ่งนี้แทน:
from c in db.Customers where c.Name == "John Doe"
select new { c.Id, c.Name }
และจะนี้สำหรับ ORMs ทันสมัยที่สุดสั่งให้ไปเท่านั้นและสอบถามId
และName
คอลัมน์ที่ได้รับความคุ้มครองโดยสันนิษฐานดัชนี ( แต่ไม่Email
, LastActivityDate
หรืออะไรก็ตามคอลัมน์อื่น ๆ ที่คุณเกิดขึ้นที่จะติดอยู่ในนั้น)
นอกจากนี้ยังง่ายมากที่จะกระจายผลประโยชน์จากการจัดทำดัชนีโดยใช้ภาคแสดงที่ไม่เหมาะสม ตัวอย่างเช่น:
from c in db.Customers where c.Name.Contains("Doe")
... มีลักษณะเกือบจะเหมือนกับคำก่อนหน้าของเรา LIKE '%Doe%'
แต่ในความเป็นจริงจะมีผลในตารางหรือดัชนีสแกนเพราะมันแปลว่า ในทำนองเดียวกันแบบสอบถามอื่นที่ดูเรียบง่ายอย่างน่าสงสัยคือ:
from c in db.Customers where (maxDate == null) || (c.BirthDate >= maxDate)
สมมติว่าคุณมีดัชนีในภาคBirthDate
แสดงนี้มีโอกาสที่ดีที่จะทำให้มันไร้ประโยชน์อย่างสมบูรณ์ โปรแกรมเมอร์สมมุติฐานของเราที่นี่พยายามสร้างแบบสอบถามแบบไดนามิก ("กรองวันเกิดเฉพาะในกรณีที่ระบุพารามิเตอร์") แต่นี่ไม่ใช่วิธีที่ถูกต้องที่จะทำ เขียนแบบนี้แทน:
from c in db.Customers where c.BirthDate >= (maxDate ?? DateTime.MinValue)
... ตอนนี้โปรแกรม DB รู้วิธีการทำให้เป็นพารามิเตอร์นี้และทำดัชนีการค้นหา การเปลี่ยนแปลงเล็กน้อยในนิพจน์เคียวรีเล็กน้อยหนึ่งรายการอาจส่งผลกระทบต่อประสิทธิภาพการทำงานอย่างมาก
น่าเสียดายที่ LINQ โดยทั่วไปทำให้ทุกอย่างง่ายเกินไปในการเขียนข้อความค้นหาที่ไม่ดีเช่นนี้เพราะบางครั้งผู้ให้บริการจะสามารถเดาได้ว่าคุณพยายามทำอะไรและเพิ่มประสิทธิภาพของแบบสอบถามและบางครั้งพวกเขาก็ไม่ได้ ดังนั้นคุณจะได้ผลลัพธ์ที่ไม่สอดคล้องอย่างน่าผิดหวังซึ่งจะทำให้เห็นได้อย่างชัดเจน (สำหรับ DBA ที่มีประสบการณ์แล้ว) คุณเพิ่งเขียน SQL แบบเก่าธรรมดา
โดยพื้นฐานแล้วทั้งหมดนั้นเกิดขึ้นจากข้อเท็จจริงที่ว่าคุณต้องจับตาดูทั้ง SQL ที่สร้างขึ้นและแผนการดำเนินการที่พวกเขานำไปสู่และหากคุณไม่ได้รับผลลัพธ์ตามที่คาดหวังอย่ากลัวที่จะหลีกเลี่ยง ORM เลเยอร์นาน ๆ และโค้ด SQL สิ่งนี้จะเกิดขึ้นกับORM ใด ๆไม่ใช่แค่ EF
การทำธุรกรรมและการล็อค
คุณต้องการแสดงข้อมูลที่เป็นปัจจุบันถึงมิลลิวินาทีหรือไม่ อาจจะ - มันขึ้นอยู่กับ - แต่อาจจะไม่ น่าเศร้าEntity Framework ไม่ได้ให้คุณnolock
คุณสามารถใช้ได้เฉพาะREAD UNCOMMITTED
ในระดับธุรกรรม (ไม่ใช่ระดับตาราง) ในความเป็นจริงไม่มี ORM ใดที่เชื่อถือได้เป็นพิเศษเกี่ยวกับเรื่องนี้ หากคุณต้องการอ่านสกปรกคุณต้องเลื่อนลงไปที่ระดับ SQL และเขียนแบบสอบถามเฉพาะกิจหรือขั้นตอนการจัดเก็บ ดังนั้นสิ่งที่เดือดร้อนลงไปอีกคือความง่ายสำหรับคุณที่จะทำสิ่งนั้นภายในกรอบ
Entity Framework มาไกลในเรื่องนี้ - รุ่นที่ 1 ของ EF (ใน. NET 3.5) แย่มากทำให้ยากที่จะฝ่าฝืน "เอนทิตี้" สิ่งที่เป็นนามธรรม แต่ตอนนี้คุณมีExecuteStoreQueryและTranslateแล้วจริงๆ ก็ไม่เลวนะ. ทำความรู้จักกับคนเหล่านี้เพราะคุณจะใช้พวกเขาอย่างมาก
นอกจากนี้ยังมีปัญหาเรื่องการล็อกการเขียนและการหยุดชะงักและวิธีปฏิบัติทั่วไปของการล็อกโฮลดิ้งในฐานข้อมูลสำหรับเวลาน้อยที่สุดเท่าที่จะทำได้ ในเรื่องนี้ ORMs (รวมถึง Entity Framework) จริงมีแนวโน้มที่จะดีขึ้นกว่า SQL ดิบเพราะพวกเขาแค็ปซูหน่วยงานรูปแบบซึ่งใน EF เป็นSaveChanges กล่าวอีกนัยหนึ่งคุณสามารถ "แทรก" หรือ "อัปเดต" หรือ "ลบ" หน่วยงานลงในเนื้อหาของหัวใจของคุณได้ทุกเมื่อที่คุณต้องการความปลอดภัยในความรู้ที่ไม่มีการเปลี่ยนแปลงใด ๆ จะถูกผลักไปยังฐานข้อมูล
โปรดทราบว่า UOW ไม่เหมือนกับธุรกรรมที่ใช้เวลานาน UOW ยังคงใช้คุณสมบัติการทำงานพร้อมกันในแง่ของการออมและติดตามการเปลี่ยนแปลงทั้งหมดในหน่วยความจำ ไม่ใช่คำสั่ง DML เดียวที่ถูกปล่อยออกมาจนกว่าการกระทำสุดท้าย สิ่งนี้จะช่วยให้การทำธุรกรรมครั้งที่ต่ำที่สุด หากคุณสร้างแอปพลิเคชันของคุณโดยใช้ SQL ดิบมันค่อนข้างยากที่จะบรรลุพฤติกรรมที่เลื่อนออกไปนี้
สิ่งนี้หมายความว่าอย่างไรสำหรับ EF โดยเฉพาะ: ทำให้หน่วยงานของคุณหยาบที่สุดเท่าที่จะเป็นไปได้และอย่าผูกมัดจนกว่าคุณจะต้องการจริงๆ ทำสิ่งนี้และคุณจะจบลงด้วยการช่วงชิงล็อกที่ต่ำกว่าที่คุณจะใช้คำสั่ง ADO.NET ทีละคำสั่งแบบสุ่ม
EF นั้นสมบูรณ์แบบสำหรับแอพพลิเคชั่นที่มีปริมาณการใช้งานสูงและมีประสิทธิภาพเช่นเดียวกับกรอบอื่น ๆ ที่ใช้ได้กับแอพพลิเคชั่นที่มีปริมาณการใช้งานสูงและมีประสิทธิภาพสูง สิ่งที่สำคัญคือคุณใช้มันอย่างไร นี่เป็นการเปรียบเทียบอย่างรวดเร็วของเฟรมเวิร์กที่ได้รับความนิยมมากที่สุดและฟีเจอร์ที่เสนอในแง่ของประสิทธิภาพ (คำอธิบาย: N = ไม่รองรับ, P = บางส่วน, Y = ใช่ / รองรับ):
อย่างที่คุณเห็น EF4 (เวอร์ชั่นปัจจุบัน) ไม่ได้เลวร้ายนัก แต่มันอาจจะไม่ดีที่สุดถ้าประสิทธิภาพเป็นประเด็นหลักของคุณ NHibernate นั้นมีความเป็นผู้ใหญ่มากกว่าในพื้นที่นี้และแม้แต่ Linq to SQL ยังมีฟีเจอร์ที่ช่วยเพิ่มประสิทธิภาพที่ EF ยังไม่ได้ทำ Raw ADO.NET มักจะเร็วขึ้นสำหรับสถานการณ์การเข้าถึงข้อมูลที่เฉพาะเจาะจงมากแต่เมื่อคุณรวมทุกส่วนเข้าด้วยกันจริงๆแล้วมันไม่ได้ให้ประโยชน์ที่สำคัญมากมายที่คุณได้รับจากกรอบงานที่หลากหลาย