SqlConnection จะเข้าร่วมใน TransactionScope Ambient โดยอัตโนมัติอย่างไร


201

SqlConnection มีความหมายว่าอะไร "เกณฑ์" ในการทำธุรกรรม? มันหมายความว่าคำสั่งที่ฉันใช้ในการเชื่อมต่อจะมีส่วนร่วมในการทำธุรกรรมหรือไม่?

ถ้าเป็นเช่นนั้นภายใต้สถานการณ์ใด SqlConnection จะเข้าร่วมโดยอัตโนมัติในธุรกรรม TransactionScope แวดล้อม?

ดูคำถามในความคิดเห็นของรหัส การเดาของฉันต่อคำตอบของคำถามแต่ละข้อนั้นตามด้วยคำถามแต่ละข้อในวงเล็บ

สถานการณ์ที่ 1: การเปิดการเชื่อมต่อภายในขอบเขตการทำธุรกรรม

using (TransactionScope scope = new TransactionScope())
using (SqlConnection conn = ConnectToDB())
{   
    // Q1: Is connection automatically enlisted in transaction? (Yes?)
    //
    // Q2: If I open (and run commands on) a second connection now,
    // with an identical connection string,
    // what, if any, is the relationship of this second connection to the first?
    //
    // Q3: Will this second connection's automatic enlistment
    // in the current transaction scope cause the transaction to be
    // escalated to a distributed transaction? (Yes?)
}

สถานการณ์ที่ 2: การใช้การเชื่อมต่อภายในขอบเขตธุรกรรมที่เปิดอยู่ด้านนอกของขอบเขต

//Assume no ambient transaction active now
SqlConnection new_or_existing_connection = ConnectToDB(); //or passed in as method parameter
using (TransactionScope scope = new TransactionScope())
{
    // Connection was opened before transaction scope was created
    // Q4: If I start executing commands on the connection now,
    // will it automatically become enlisted in the current transaction scope? (No?)
    //
    // Q5: If not enlisted, will commands I execute on the connection now
    // participate in the ambient transaction? (No?)
    //
    // Q6: If commands on this connection are
    // not participating in the current transaction, will they be committed
    // even if rollback the current transaction scope? (Yes?)
    //
    // If my thoughts are correct, all of the above is disturbing,
    // because it would look like I'm executing commands
    // in a transaction scope, when in fact I'm not at all, 
    // until I do the following...
    //
    // Now enlisting existing connection in current transaction
    conn.EnlistTransaction( Transaction.Current );
    //
    // Q7: Does the above method explicitly enlist the pre-existing connection
    // in the current ambient transaction, so that commands I
    // execute on the connection now participate in the
    // ambient transaction? (Yes?)
    //
    // Q8: If the existing connection was already enlisted in a transaction
    // when I called the above method, what would happen?  Might an error be thrown? (Probably?)
    //
    // Q9: If the existing connection was already enlisted in a transaction
    // and I did NOT call the above method to enlist it, would any commands
    // I execute on it participate in it's existing transaction rather than
    // the current transaction scope. (Yes?)
}

คำตอบ:


188

ฉันทำแบบทดสอบมาแล้วตั้งแต่ถามคำถามนี้และพบว่าส่วนใหญ่ถ้าไม่ใช่คำตอบทั้งหมดด้วยตัวเองเพราะไม่มีใครตอบ โปรดแจ้งให้เราทราบหากฉันไม่ได้รับอะไรเลย

ไตรมาสที่ 1 ใช่เว้นแต่จะระบุ "enlist = false" ในสตริงการเชื่อมต่อ กลุ่มการเชื่อมต่อค้นหาการเชื่อมต่อที่ใช้งานได้ การเชื่อมต่อที่ใช้งานได้คือการเชื่อมต่อที่ไม่ได้อยู่ในธุรกรรมหรือเป็นการเชื่อมต่อในธุรกรรมเดียวกัน

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

ไตรมาสที่ 3 ใช่มันได้รับการส่งต่อไปยังธุรกรรมแบบกระจายดังนั้นการสมัครเข้าใช้มากกว่าหนึ่งการเชื่อมต่อแม้ว่าจะมีสตริงการเชื่อมต่อเดียวกันทำให้กลายเป็นธุรกรรมแบบกระจายซึ่งสามารถยืนยันได้โดยการตรวจสอบ GUID ที่ไม่ใช่ศูนย์ที่ Transaction.Current.TransactionInformation .DistributedIdentifier * อัปเดต: ฉันอ่านบางที่ว่านี้ได้รับการแก้ไขใน SQL Server 2008 ดังนั้น MSDTC จะไม่ถูกใช้เมื่อมีการใช้สตริงการเชื่อมต่อเดียวกันสำหรับการเชื่อมต่อทั้งสอง (ตราบใดที่การเชื่อมต่อทั้งสองไม่เปิดในเวลาเดียวกัน) ที่ช่วยให้คุณเปิดการเชื่อมต่อและปิดได้หลายครั้งภายในการทำธุรกรรมซึ่งสามารถใช้ประโยชน์จากพูลการเชื่อมต่อได้ดีขึ้นโดยการเปิดการเชื่อมต่อให้ช้าที่สุดและปิดพวกเขาโดยเร็วที่สุด

ไตรมาสที่ 4 ไม่การเชื่อมต่อเปิดขึ้นเมื่อไม่มีขอบเขตการทำธุรกรรมที่ใช้งานอยู่จะไม่เข้าร่วมโดยอัตโนมัติในขอบเขตธุรกรรมที่สร้างขึ้นใหม่

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

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

Q7 ใช่. การเชื่อมต่อที่มีอยู่สามารถลงทะเบียนอย่างชัดเจนในขอบเขตธุรกรรมปัจจุบันโดยการโทร EnlistTransaction (Transaction.Current) นอกจากนี้คุณยังสามารถสมัครเข้าใช้การเชื่อมต่อบนเธรดแยกต่างหากในธุรกรรมโดยใช้ DependentTransaction แต่ก่อนหน้านี้ฉันไม่แน่ใจว่าการเชื่อมต่อสองรายการที่เกี่ยวข้องในธุรกรรมเดียวกันกับฐานข้อมูลเดียวกันอาจโต้ตอบ ... และอาจเกิดข้อผิดพลาดและ แน่นอนว่าการเชื่อมต่อที่สองทำให้ธุรกรรมขยายไปสู่ธุรกรรมแบบกระจาย

Q8 ข้อผิดพลาดอาจถูกโยน ถ้า TransactionScopeOption.Required ถูกใช้และการเชื่อมต่อได้รับการลงทะเบียนแล้วในธุรกรรมขอบเขตธุรกรรม อันที่จริงไม่มีธุรกรรมใหม่ที่สร้างขึ้นสำหรับขอบเขตและจำนวนธุรกรรม (@@ trancount) จะไม่เพิ่มขึ้น อย่างไรก็ตามหากคุณใช้ TransactionScopeOption.RequiresNew คุณจะได้รับข้อความแสดงข้อผิดพลาดที่เป็นประโยชน์เมื่อพยายามเข้าร่วมการเชื่อมต่อในธุรกรรมขอบเขตธุรกรรมใหม่: "การเชื่อมต่อในปัจจุบันมีธุรกรรมเป็นรายการเสร็จแล้วธุรกรรมปัจจุบันและลองอีกครั้ง" และใช่ถ้าคุณทำธุรกรรมเสร็จสิ้นการเชื่อมต่อนั้นเข้าร่วมคุณสามารถเข้าใช้การเชื่อมต่อในธุรกรรมใหม่ได้อย่างปลอดภัย อัปเดต: หากก่อนหน้านี้คุณเรียกว่า BeginTransaction ในการเชื่อมต่อข้อผิดพลาดที่แตกต่างกันเล็กน้อยจะถูกส่งออกมาเมื่อคุณพยายามเข้าร่วมในธุรกรรมขอบเขตธุรกรรมใหม่: "ไม่สามารถเข้าร่วมในธุรกรรมได้เนื่องจากธุรกรรมในท้องถิ่นกำลังดำเนินการอยู่ ลองใหม่อีกครั้ง." ในอีกทางหนึ่งคุณสามารถโทร BeginTransaction บน SqlConnection ได้อย่างปลอดภัยขณะที่เกณฑ์ในธุรกรรมขอบเขตธุรกรรมและที่จริงจะเพิ่ม @@ trancount หนึ่งโดยไม่ใช้ตัวเลือกที่จำเป็นของขอบเขตธุรกรรมซ้อนซึ่งไม่ทำให้เกิด เพิ่มขึ้น. ที่น่าสนใจถ้าคุณดำเนินการต่อเพื่อสร้างขอบเขตการทำธุรกรรมที่ซ้อนกันอีกครั้งด้วยตัวเลือกที่จำเป็นคุณจะไม่ได้รับข้อผิดพลาด

Q9 ใช่. คำสั่งมีส่วนร่วมในการทำธุรกรรมใด ๆ ที่การเชื่อมต่อที่ถูกเกณฑ์เข้าโดยไม่คำนึงถึงสิ่งที่ขอบเขตการทำธุรกรรมที่ใช้งานอยู่ในรหัส C #


11
หลังจากเขียนคำตอบไปที่ Q8 ฉันรู้ว่าสิ่งนี้เริ่มดูซับซ้อนเหมือนกฎของเวทย์มนตร์: การรวบรวม! ยกเว้นสิ่งนี้จะแย่ลงเนื่องจากเอกสาร TransactionScope ไม่ได้อธิบายสิ่งนี้
Triynko

สำหรับไตรมาสที่ 3 คุณเปิดการเชื่อมต่อสองรายการพร้อมกันโดยใช้สตริงการเชื่อมต่อเดียวกันหรือไม่ ถ้าเป็นเช่นนั้นนั่นจะเป็นธุรกรรมที่ถูกแจกจ่าย (แม้กระทั่งกับ SQL Server 2008)
Randy สนับสนุน Monica

2
ไม่ฉันจะแก้ไขโพสต์เพื่อให้ความกระจ่าง ความเข้าใจของฉันคือการเปิดการเชื่อมต่อสองครั้งในเวลาเดียวกันจะทำให้เกิดธุรกรรมแบบกระจายโดยไม่คำนึงถึงรุ่นของ SQL Server ก่อน SQL 2008 การเปิดการเชื่อมต่อครั้งเดียวเท่านั้นด้วยสตริงการเชื่อมต่อเดียวกันจะยังคงทำให้เกิด DT แต่ด้วย SQL 2008 การเปิดการเชื่อมต่อครั้งละหนึ่งรายการ (ไม่เคยเปิดสองครั้งพร้อมกัน) ด้วยสตริงการเชื่อมต่อเดียวกันจะไม่ทำให้ DT
Triynko

1
เพื่อชี้แจงคำตอบของคุณสำหรับ Q2 คำสั่งทั้งสองควรรันได้ดีหากทำตามลำดับบนเธรดเดียวกัน
Jared Moore

2
ในปัญหาการเลื่อนระดับ Q3 สำหรับสตริงการเชื่อมต่อที่เหมือนกันใน SQL 2008 นี่คือการอ้างอิง MSDN: msdn.microsoft.com/en-us/library/ms172070(v=vs.90).aspx
pseudocoder

19

การทำงานที่ดี Triynko คำตอบของคุณทุกคนดูค่อนข้างแม่นยำและสมบูรณ์แบบสำหรับฉัน สิ่งอื่น ๆ ที่ฉันอยากจะชี้ให้เห็น:

(1)การเกณฑ์ทหาร

ในรหัสของคุณด้านบนคุณ (ถูกต้อง) แสดงการเข้าร่วมแบบแมนนวลเช่นนี้:

using (SqlConnection conn = new SqlConnection(connStr))
{
    conn.Open();
    using (TransactionScope ts = new TransactionScope())
    {
        conn.EnlistTransaction(Transaction.Current);
    }
}

อย่างไรก็ตามมันเป็นไปได้ที่จะทำเช่นนี้โดยใช้ Enlist = false ในสตริงการเชื่อมต่อ

string connStr = "...; Enlist = false";
using (TransactionScope ts = new TransactionScope())
{
    using (SqlConnection conn1 = new SqlConnection(connStr))
    {
        conn1.Open();
        conn1.EnlistTransaction(Transaction.Current);
    }

    using (SqlConnection conn2 = new SqlConnection(connStr))
    {
        conn2.Open();
        conn2.EnlistTransaction(Transaction.Current);
    }
}

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

(2)ก่อนสุทธิ 4.0 ผมขอแนะนำการตั้งค่า"การทำธุรกรรมการผูก = ชัดเจนยกเลิกการเชื่อมโยง" ในสตริงการเชื่อมต่อ ปัญหานี้ได้รับการแก้ไขใน. Net 4.0 จึงทำให้ Unbind Unbind ไม่จำเป็นทั้งหมด

(3) การกลิ้งของคุณCommittableTransactionและการตั้งค่าTransaction.Currentเป็นสิ่งเดียวกันกับสิ่งที่TransactionScopeทำ สิ่งนี้ไม่ค่อยมีประโยชน์จริง ๆ เพียงแค่ FYI

(4) Transaction.Currentเป็นเธรดแบบคงที่ ซึ่งหมายความว่ามีการตั้งค่าเฉพาะในหัวข้อที่สร้างTransaction.Current TransactionScopeดังนั้นไม่สามารถใช้หลายเธรดที่ดำเนินการเหมือนกันTransactionScope(อาจใช้Task)


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

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

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

อย่างไรก็ตามนั่นคือสิ่งที่ฉันบอกใบ้ไว้ในคำตอบของฉันในไตรมาสที่ 1 เมื่อฉันพูดว่ามันถูกเกณฑ์เว้นแต่ว่า "Enlist = false" ระบุไว้ในสตริงการเชื่อมต่อจากนั้นพูดคุยเกี่ยวกับวิธีที่พูลพบการเชื่อมต่อที่เหมาะสม
Triynko

ถ้าคุณไปที่ลิงก์ในคำตอบของฉันไปที่ Q2 คุณจะเห็นว่าในขณะที่ธุรกรรมปัจจุบันเป็นเอกลักษณ์ของแต่ละเธรดคุณสามารถรับการอ้างอิงในเธรดหนึ่งและส่งไปยังเธรดอื่นได้อย่างง่ายดาย อย่างไรก็ตามการเข้าถึง TST จากสองเธรดที่แตกต่างกันส่งผลให้เกิดข้อผิดพลาดเฉพาะ "บริบทการทำธุรกรรมที่ใช้โดยเซสชันอื่น" สำหรับ TST แบบหลายเธรดคุณต้องสร้าง DependantTransaction แต่ ณ จุดนั้นจะต้องเป็นธุรกรรมแบบกระจายเนื่องจากคุณต้องการการเชื่อมต่ออิสระครั้งที่สองเพื่อเรียกใช้คำสั่งพร้อมกันและ MSDTC จริง ๆ เพื่อประสานงานทั้งสอง
Triynko

1

อีกหนึ่งสถานการณ์ที่แปลกประหลาดที่เราเคยเห็นคือถ้าคุณสร้างEntityConnectionStringBuilderมันจะสร้างความสับสนTransactionScope.Currentและ (เราคิดว่า) เข้าร่วมในการทำธุรกรรม เราได้ตั้งข้อสังเกตนี้ในการดีบักที่TransactionScope.Current's current.TransactionInformation.internalTransactionแสดงให้เห็นว่าenlistmentCount == 1ก่อนที่จะสร้างและenlistmentCount == 2หลังจากนั้น

เพื่อหลีกเลี่ยงปัญหานี้ให้สร้างขึ้นภายใน

using (new TransactionScope(TransactionScopeOption.Suppress))

และอาจอยู่นอกขอบเขตการดำเนินการของคุณ (เรากำลังสร้างมันขึ้นมาทุกครั้งที่เราต้องการการเชื่อมต่อ)

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