ฉันควรยอมรับหรือย้อนกลับธุรกรรมการอ่านหรือไม่?


96

ฉันมีแบบสอบถามสำหรับอ่านที่ฉันดำเนินการภายในธุรกรรมเพื่อที่ฉันจะได้ระบุระดับการแยก เมื่อสอบถามเสร็จแล้วควรทำอย่างไร?

  • ทำธุรกรรม
  • ย้อนกลับธุรกรรม
  • ไม่ต้องทำอะไรเลย (ซึ่งจะทำให้ธุรกรรมถูกย้อนกลับในตอนท้ายของบล็อกการใช้งาน)

อะไรคือผลกระทบของการทำแต่ละอย่าง?

using (IDbConnection connection = ConnectionFactory.CreateConnection())
{
    using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
    {
        using (IDbCommand command = connection.CreateCommand())
        {
            command.Transaction = transaction;
            command.CommandText = "SELECT * FROM SomeTable";
            using (IDataReader reader = command.ExecuteReader())
            {
                // Read the results
            }
        }

        // To commit, or not to commit?
    }
}

แก้ไข: คำถามไม่ใช่ว่าควรใช้ธุรกรรมหรือมีวิธีอื่นในการตั้งค่าระดับธุรกรรมหรือไม่ คำถามคือถ้ามันสร้างความแตกต่างที่ธุรกรรมที่ไม่มีการปรับเปลี่ยนอะไรจะถูกผูกมัดหรือย้อนกลับ มีประสิทธิภาพแตกต่างกันหรือไม่? มีผลต่อการเชื่อมต่ออื่น ๆ หรือไม่? ความแตกต่างอื่น ๆ ?


1
คุณอาจทราบเกี่ยวกับเรื่องนี้แล้ว แต่จากตัวอย่างที่คุณให้ไว้คุณอาจได้ผลลัพธ์ที่เทียบเท่ากันโดยดำเนินการค้นหาง่ายๆ: SELECT * FROM SomeTable with NOLOCK
JasonTrue

@ Stefan ดูเหมือนว่าพวกเราส่วนใหญ่สงสัยว่าทำไมคุณถึงรบกวนการทำธุรกรรมในการดำเนินการอ่านอย่างเดียว คุณช่วยแจ้งให้เราทราบได้ไหมว่าคุณรู้จัก NOLOCK และถ้าคุณเป็นเช่นนั้นทำไมคุณถึงไม่ไปเส้นทางนั้น
StingyJack

ฉันรู้เกี่ยวกับ NOLOCK แต่ระบบนี้ทำงานกับฐานข้อมูลที่แตกต่างกันเช่นเดียวกับ SQL Server ดังนั้นฉันจึงพยายามหลีกเลี่ยงคำแนะนำการล็อกเฉพาะของ SQL Server นี่เป็นคำถามที่เกิดจากความอยากรู้อยากเห็นมากกว่าสิ่งอื่นใดเนื่องจากแอปพลิเคชันทำงานได้ดีกับโค้ดด้านบน
Stefan Moser

อ่าในกรณีนั้นฉันกำลังลบแท็ก sqlserver เพราะนั่นแสดงว่า MSSqlServer เป็นผลิตภัณฑ์เป้าหมาย
StingyJack

@StingyJack - คุณพูดถูกฉันไม่ควรใช้แท็ก sqlserver
Stefan Moser

คำตอบ:


52

คุณกระทำ ระยะเวลา ไม่มีทางเลือกอื่นที่สมเหตุสมผล หากคุณเริ่มทำธุรกรรมคุณควรปิด การคอมมิตปลดล็อคใด ๆ ที่คุณอาจมีและมีความสมเหตุสมผลพอ ๆ กันกับระดับการแยกแบบ ReadUncommitted หรือ Serial ได้ การอาศัยการย้อนกลับโดยนัย - ในขณะที่อาจเทียบเท่าในทางเทคนิค - เป็นเพียงรูปแบบที่ไม่ดี

หากยังไม่ทำให้คุณมั่นใจลองนึกภาพผู้ชายคนต่อไปที่แทรกข้อความอัปเดตไว้ตรงกลางโค้ดของคุณและต้องติดตามการย้อนกลับโดยปริยายที่เกิดขึ้นและลบข้อมูลของเขา


45
มีทางเลือกที่สมเหตุสมผล - ย้อนกลับ การย้อนกลับอย่างชัดเจนนั่นคือ หากคุณไม่ได้ตั้งใจที่จะเปลี่ยนแปลงอะไรเลยการย้อนกลับจะช่วยให้แน่ใจว่าทุกอย่างจะถูกยกเลิก แน่นอนว่าไม่ควรมีการเปลี่ยนแปลงใด ๆ การย้อนกลับรับประกันว่า
Jonathan Leffler

2
DBMS ที่แตกต่างกันอาจมีความหมาย 'การทำธุรกรรมโดยนัย' ที่แตกต่างกัน IBM Informix (และฉันเชื่อว่า DB2) ทำการย้อนกลับโดยปริยาย จากข่าวลือ Oracle ทำการกระทำโดยปริยาย ฉันชอบการย้อนกลับโดยปริยาย
Jonathan Leffler

8
สมมติว่าฉันสร้างตารางชั่วคราวเติมข้อมูลด้วยรหัสเข้าร่วมกับตารางข้อมูลเพื่อเลือกข้อมูลที่ไปกับรหัสจากนั้นลบตารางอุณหภูมิ ฉันแค่อ่านข้อมูลจริงๆและฉันไม่สนใจว่าจะเกิดอะไรขึ้นกับตารางชั่วคราวเนื่องจากมันเป็นแบบชั่วคราว ... อะไรคือผลของการกระทำ / ย้อนกลับเมื่อไม่มีอะไรนอกจากตารางชั่วคราวและการดำเนินการอ่านที่เกี่ยวข้อง
Triynko

4
@Triynko - โดยสัญชาตญาณฉันเดาว่า ROLLBACK แพงกว่า COMMIT เป็นกรณีการใช้งานปกติและ ROLLBACK เป็นกรณีพิเศษ แต่ยกเว้นด้านวิชาการใครสนใจ? ฉันแน่ใจว่ามีคะแนนการเพิ่มประสิทธิภาพที่ดีกว่า 1,000 คะแนนสำหรับแอปของคุณ หากคุณอยากรู้อยากเห็นจริงๆคุณสามารถค้นหารหัสการจัดการธุรกรรม mySQL ได้ที่bazaar.launchpad.net/~mysql/mysql-server/mysql-6.0/annotate/…
Mark Brackett

3
@Triynko - วิธีเดียวในการปรับให้เหมาะสมคือโปรไฟล์ มันเป็นการเปลี่ยนรหัสง่ายๆไม่มีเหตุผลที่จะไม่กำหนดโปรไฟล์ทั้งสองวิธีหากคุณต้องการปรับให้เหมาะสมจริงๆ อย่าลืมอัปเดตผลการแข่งขันให้เราทราบ!
Mark Brackett

28

หากคุณไม่ได้เปลี่ยนแปลงอะไรเลยคุณสามารถใช้ COMMIT หรือ ROLLBACK ก็ได้ คนใดคนหนึ่งจะปลดล็อกการอ่านใด ๆ ที่คุณได้รับและเนื่องจากคุณยังไม่ได้ทำการเปลี่ยนแปลงอื่น ๆ จึงจะเทียบเท่า


2
ขอบคุณที่แจ้งให้เราทราบว่าเทียบเท่า ในความคิดของฉันสิ่งนี้ตอบคำถามที่แท้จริงได้ดีที่สุด
chowey

มันจะทำให้ธุรกรรมไม่ทำงานหากเราใช้การกระทำโดยไม่มีการอัปเดตจริง ฉันเพิ่งเผชิญหน้ากับมันบนเว็บไซต์สดของฉัน
มูฮัมหมัดโอเมอร์อัสลาม

7

หากคุณเริ่มทำธุรกรรมแนวทางปฏิบัติที่ดีที่สุดคือการทำธุรกรรมเสมอ หากมีข้อยกเว้นเกิดขึ้นในบล็อกการใช้งาน (ธุรกรรม) ของคุณธุรกรรมจะถูกย้อนกลับโดยอัตโนมัติ


3

IMHO สามารถสรุปการสืบค้นแบบอ่านอย่างเดียวในธุรกรรมเป็น (โดยเฉพาะใน Java) คุณสามารถบอกให้ธุรกรรมเป็น "อ่านอย่างเดียว" ซึ่งจะทำให้ไดรเวอร์ JDBC สามารถพิจารณาเพิ่มประสิทธิภาพการสืบค้นได้ (แต่ไม่จำเป็นต้องทำดังนั้นจึงไม่มีใคร จะป้องกันไม่ให้คุณออกINSERTอย่างไรก็ตาม) เช่นไดรเวอร์ Oracle จะหลีกเลี่ยงการล็อกตารางโดยสิ้นเชิงในการสืบค้นในธุรกรรมที่ทำเครื่องหมายว่าอ่านอย่างเดียวซึ่งได้รับประสิทธิภาพจำนวนมากในแอปพลิเคชันที่ขับเคลื่อนด้วยการอ่านจำนวนมาก


3

พิจารณาการทำธุรกรรมที่ซ้อนกัน

RDBMS ส่วนใหญ่ไม่สนับสนุนธุรกรรมที่ซ้อนกันหรือพยายามเลียนแบบในลักษณะที่ จำกัด

ตัวอย่างเช่นใน MS SQL Server การย้อนกลับในธุรกรรมภายใน (ซึ่งไม่ใช่ธุรกรรมจริง MS SQL Server จะนับระดับธุรกรรมเท่านั้น!) จะย้อนกลับทุกอย่างที่เกิดขึ้นในธุรกรรมที่อยู่นอกสุด (ซึ่งเป็นธุรกรรมจริง)

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

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

โปรดทราบว่านี่เป็นคำตอบทั่วไปสำหรับคำถาม ตัวอย่างโค้ดสามารถแก้ไขปัญหาเกี่ยวกับธุรกรรมภายนอกได้อย่างชาญฉลาดโดยการเปิดการเชื่อมต่อฐานข้อมูลใหม่

เกี่ยวกับประสิทธิภาพ: ขึ้นอยู่กับระดับการแยก SELECT อาจต้องใช้ระดับ LOCK และข้อมูลชั่วคราว (สแนปชอต) ที่แตกต่างกัน สิ่งนี้จะถูกล้างออกเมื่อปิดธุรกรรม ไม่สำคัญว่าจะทำผ่าน COMMIT หรือ ROLLBACK เวลา CPU ที่ใช้อาจมีความแตกต่างเล็กน้อย - COMMIT อาจจะแยกวิเคราะห์ได้เร็วกว่า ROLLBACK (น้อยกว่าสองอักขระ) และความแตกต่างเล็กน้อยอื่น ๆ เห็นได้ชัดว่านี่เป็นเรื่องจริงสำหรับการใช้งานแบบอ่านอย่างเดียวเท่านั้น!

ไม่ได้ถามหาโดยสิ้นเชิง: โปรแกรมเมอร์คนอื่นที่อาจอ่านโค้ดอาจคิดว่า ROLLBACK แสดงถึงเงื่อนไขข้อผิดพลาด


2

เป็นเพียงบันทึกด้านข้าง แต่คุณสามารถเขียนโค้ดได้ดังนี้:

using (IDbConnection connection = ConnectionFactory.CreateConnection())
using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
using (IDbCommand command = connection.CreateCommand())
{
    command.Transaction = transaction;
    command.CommandText = "SELECT * FROM SomeTable";
    using (IDataReader reader = command.ExecuteReader())
    {
        // Do something useful
    }
    // To commit, or not to commit?
}

และถ้าคุณจัดโครงสร้างใหม่เพียงเล็กน้อยคุณอาจสามารถย้ายบล็อกที่ใช้สำหรับ IDataReader ขึ้นไปด้านบนได้เช่นกัน


1

หากคุณใส่ SQL ลงในกระบวนงานที่เก็บไว้และเพิ่มสิ่งนี้ไว้เหนือแบบสอบถาม:

set transaction isolation level read uncommitted

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


1

ROLLBACK ส่วนใหญ่จะใช้ในกรณีที่เกิดข้อผิดพลาดหรือสถานการณ์พิเศษและ COMMIT ในกรณีที่ทำสำเร็จ

เราควรปิดธุรกรรมด้วย COMMIT (เพื่อความสำเร็จ) และ ROLLBACK (สำหรับความล้มเหลว) แม้ในกรณีของธุรกรรมแบบอ่านอย่างเดียวซึ่งดูเหมือนจะไม่สำคัญ ในความเป็นจริงมันมีความสำคัญสำหรับความสม่ำเสมอและการพิสูจน์ในอนาคต

ธุรกรรมแบบอ่านอย่างเดียวสามารถ "ล้มเหลว" เชิงตรรกะได้หลายวิธีตัวอย่างเช่น:

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

หากใช้ COMMIT และ ROLLBACK อย่างถูกต้องสำหรับธุรกรรมแบบอ่านอย่างเดียวจะยังคงทำงานได้ตามที่คาดไว้หากมีการเพิ่มโค้ดการเขียน DB ในบางจุดเช่นการแคชการตรวจสอบหรือสถิติ

ควรใช้ ROLLBACK โดยนัยสำหรับสถานการณ์ "ข้อผิดพลาดร้ายแรง" เท่านั้นเมื่อแอปพลิเคชันขัดข้องหรือออกโดยมีข้อผิดพลาดที่ไม่สามารถกู้คืนได้เครือข่ายขัดข้องไฟดับเป็นต้น


0

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

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


3
ขึ้นอยู่กับระดับการแยกตัวเลือกสามารถรับการล็อกที่จะบล็อกธุรกรรมอื่น ๆ
Graeme Perrow

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

1
การทำธุรกรรมจะถูกผูกมัดหรือย้อนกลับไม่ทางใดก็ทางหนึ่งดังนั้นแนวทางปฏิบัติที่ดีที่สุดคือการออกคอมมิตเสมอหากทำสำเร็จ
Neil Barnwell

0

หากคุณตั้งค่า AutoCommit เท็จแสดงว่าใช่

ในการทดลองกับ JDBC (ไดรเวอร์ Postgresql) ฉันพบว่าหากข้อความค้นหาที่เลือกหยุดทำงาน (เนื่องจากหมดเวลา) คุณจะไม่สามารถเริ่มการสืบค้นเลือกใหม่ได้เว้นแต่คุณจะย้อนกลับ


-3

คุณจำเป็นต้องปิดกั้นไม่ให้ผู้อื่นอ่านข้อมูลเดียวกันหรือไม่? ทำไมต้องใช้ธุรกรรม?

@Joel - คำถามของฉันน่าจะใช้วลีที่ดีกว่าว่า "ทำไมต้องใช้ธุรกรรมกับข้อความที่อ่าน"

@ Stefan - หากคุณจะใช้ AdHoc SQL และไม่ใช่ proc ที่จัดเก็บไว้ให้เพิ่มด้วย (NOLOCK) หลังตารางในแบบสอบถาม ด้วยวิธีนี้คุณจะไม่ต้องเสียค่าใช้จ่าย (แม้ว่าจะน้อยที่สุด) ในแอปพลิเคชันและฐานข้อมูลสำหรับธุรกรรม

SELECT * FROM SomeTable WITH (NOLOCK)

แก้ไข @ ความคิดเห็นที่ 3: เนื่องจากคุณมี "sqlserver" ในแท็กคำถามฉันจึงถือว่า MSSQLServer เป็นผลิตภัณฑ์เป้าหมาย ตอนนี้ได้รับการชี้แจงในประเด็นนั้นแล้วฉันได้แก้ไขแท็กเพื่อลบการอ้างอิงผลิตภัณฑ์ที่เฉพาะเจาะจง

ฉันยังไม่แน่ใจว่าทำไมคุณถึงต้องการทำธุรกรรมกับ read op ตั้งแต่แรก


1
ไปยังระดับการแยกที่ตั้งไว้พร้อมกัน คุณสามารถใช้ธุรกรรมเพื่อลดจำนวนการล็อกสำหรับแบบสอบถามได้จริง
Joel Coehoorn

1
ฉันใช้ธุรกรรมเพื่อให้สามารถใช้ระดับการแยกที่ต่ำกว่าและลดการล็อกได้
Stefan Moser

@StingyJack - รหัสนี้สามารถดำเนินการกับฐานข้อมูลที่แตกต่างกันจำนวนมากดังนั้น NOLOCK จึงไม่ใช่ตัวเลือก
Stefan Moser

-3

ในตัวอย่างโค้ดของคุณที่คุณมี

  1. // ทำสิ่งที่มีประโยชน์

    คุณกำลังเรียกใช้คำสั่ง SQL ที่เปลี่ยนแปลงข้อมูลหรือไม่?

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

แต่คุณไม่สามารถยอมรับหรือย้อนกลับอะไรได้เลยหากคำสั่ง SQL ของคุณไม่ได้เปลี่ยนแปลงอะไรเลย

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

หากสิ่งที่คุณต้องการทำคือตั้งค่าระดับการแยกธุรกรรมจากนั้นตั้งค่า CommandText ของคำสั่งเป็น "Set Transaction Isolation level Repeatable Read" (หรือระดับใดก็ได้ที่คุณต้องการ) ตั้งค่า CommandType เป็น CommandType.Text และดำเนินการคำสั่ง (คุณสามารถใช้ Command.ExecuteNonQuery ())

หมายเหตุ: หากคุณกำลังทำคำสั่งการอ่านหลายคำสั่งและต้องการให้ทุกคน "เห็น" สถานะเดียวกันของฐานข้อมูลเป็นสถานะแรกคุณต้องตั้งค่าระดับการแยกด้านบนการอ่านซ้ำหรือต่อลำดับได้ ...


// ทำสิ่งที่มีประโยชน์ไม่เปลี่ยนแปลงข้อมูลใด ๆ เพียงแค่อ่าน สิ่งที่ฉันต้องการทำคือระบุระดับการแยกของแบบสอบถาม
Stefan Moser

จากนั้นคุณสามารถทำได้โดยไม่ต้องเริ่มต้นธุรกรรมจากไคลเอนต์อย่างชัดเจน ... เพียงดำเนินการสตริง sql "Set Transaction Isolation Level ReadUncommitted", "... Read Committed", "... RepeatableRead", "... Snapshot" หรือ "... ต่อเนื่องได้" "ตั้งค่าระดับการแยกที่อ่านได้"
Charles Bretana

3
ธุรกรรมยังคงมีความสำคัญแม้ว่าคุณจะอ่านอยู่ก็ตาม หากคุณต้องการดำเนินการอ่านหลาย ๆ ครั้งการดำเนินการภายในธุรกรรมจะช่วยให้มั่นใจได้ว่ามีความสอดคล้องกัน การทำโดยไม่มีใครจะไม่ทำ
MarkR

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