Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
İşlemler, birden fazla işlemi atomik bir birimde gruplar. Ya tüm işlemler başarıyla tamamlanır (commit edilir) ya da hiçbirinin etkisi olmaz (rollback yapılır). Bu makale, sürücüyle go-mssqldb yapılan işlemlerin nasıl kullanılacağını, izolasyon seviyeleri, hata yönetimi ve üretim uygulamaları için kalıplar dahil olmak üzere ele alır.
Bu makaledeki örnekler AdventureWorks2025 örnek veritabanıyla karşılaştırılıyor. Yazma odaklı örnekler HumanResources.Department, Production.ProductCategory, Production.ProductSubcategory ve Production.ProductInventory öğelerini hedefler.
Bir işlem başlatın
Bir işlem başlatmak için db.BeginTx kullanın. Geri dönen *sql.Tx cihaz, işlem süresi boyunca havuzdan tek bir bağlantıyı pinler:
tx, err := db.BeginTx(ctx, nil) // nil uses the default isolation level
if err != nil {
return err
}
defer tx.Rollback() // No-op if tx.Commit() succeeds first.
_, err = tx.ExecContext(ctx, "INSERT INTO HumanResources.Department (Name, GroupName) VALUES (@p1, @p2)",
sql.Named("p1", name),
sql.Named("p2", groupName))
if err != nil {
return err
}
return tx.Commit()
Important
Her zaman defer tx.Rollback() işleminden hemen sonra BeginTx çağırın.
Commit() başarılı olursa, ertelenmiş Rollback() işlem yapmaz.
Commit() öncesinde herhangi bir hata oluşursa, ertelenmiş Rollback(), işlemin açık kalmamasını ve bağlantının onaylanmamış durumda havuza geri dönmemesini sağlar.
Yalıtım düzeyleri
SQL Server, eşzamanlı işlemlerin nasıl etkileşime girdiğini kontrol eden birkaç izolasyon seviyesini destekler. İzolasyon seviyesini şu sql.TxOptionsşekilde ayarlayın:
tx, err := db.BeginTx(ctx, &sql.TxOptions{
Isolation: sql.LevelReadCommitted,
})
İzolasyon seviyesi karşılaştırması
| Yalıtım düzeyi | Kirli okumalar | Tekrar Edilemez Okumalar | Hayalet okumaları | Performans etkisi | Şu durumlarda kullanın: |
|---|---|---|---|---|---|
sql.LevelReadUncommitted |
Evet | Evet | Evet | En düşük genel gider | Yaklaşık sayılar, izleme gösterge panelleri. Veri doğruluğu kritik değil. |
sql.LevelReadCommitted |
Hayır | Evet | Evet | Varsayılan. Çoğu iş yükü için iyi. | Genel OLTP iş yükleri. Varsayılan ve önerilen başlangıç noktası. |
sql.LevelRepeatableRead |
Hayır | Hayır | Evet | Orta. Kilitleri daha uzun tutar. | İşlem boyunca aynı satırlar için tutarlı değerler görmesi gereken okuma işlemleri. |
sql.LevelSerializable |
Hayır | Hayır | Hayır | En yüksek. Menzil kilitleri eşzamanlı girişleri engeller. | Finansal işlemler, envanter yönetimi, hayalet okuma her yer kabul edilemez. |
sql.LevelSnapshot |
Hayır | Hayır | Hayır | Sıralı versiyonlama kullanır tempdb. Engelleme yok. |
Okuma ağırlıklı iş yükleri, yazarları engellemeden zaman içinde tutarlılık gerektiriyor. |
Note
sql.LevelSnapshot veritabanında anlık görüntü izolasyonunun etkinleştirilmesi gerekir: ALTER DATABASE AdventureWorks2025 SET ALLOW_SNAPSHOT_ISOLATION ON.
Örnek: Read committed ve serileştirilebilir
İzolasyon seviyesini belirtin sql.TxOptions:
// Read Committed (default) - suitable for most operations.
tx1, err := db.BeginTx(ctx, &sql.TxOptions{
Isolation: sql.LevelReadCommitted,
})
// Serializable - prevents phantom reads in financial calculations.
tx2, err := db.BeginTx(ctx, &sql.TxOptions{
Isolation: sql.LevelSerializable,
})
Salt okunur yönlendirme
Sürücü, sql.TxOptions.ReadOnly öğesini desteklemiyor.
ReadOnly: true iletilirse, BeginTx bir hata döndürür.
AlwaysOn salt okunur yönlendirmesi için, bağlantıyı açtığınızda bağlantı dizesinde applicationintent=ReadOnly ayarını yapın:
sqlserver://listener.example.com?database=AdventureWorks2025&applicationintent=ReadOnly
Bu bağlantı seviyesindeki bir ayardır. Uygun oturumları okunabilir bir ikincile yönlendirebilir, ancak mevcut bir işlemi salt okunur hâle getirmez. Veri yazmaması gereken iş yükleri için özel bir yalnızca okunabilir bağlantı veya en az ayrıcalıklı kimlik bilgileri kullanın.
İşlemlerde hata yönetimi
İşlemlerdeki hataları dikkatlice ele alın. Herhangi bir komut başarısız olursa, işlem tamamen geri alınmalıdır. Bir hata sonrası başka ifadelerle devam etmeye çalışmayın:
func createCategory(ctx context.Context, db *sql.DB, category Category) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("begin transaction: %w", err)
}
defer tx.Rollback()
var categoryId int64
err = tx.QueryRowContext(ctx, `
INSERT INTO Production.ProductCategory (Name)
OUTPUT INSERTED.ProductCategoryID
VALUES (@name)`,
sql.Named("name", category.Name)).Scan(&categoryId)
if err != nil {
return fmt.Errorf("insert category: %w", err)
}
// Insert subcategories under the new category.
for _, sub := range category.Subcategories {
_, err = tx.ExecContext(ctx, `
INSERT INTO Production.ProductSubcategory (ProductCategoryID, Name)
VALUES (@catId, @name)`,
sql.Named("catId", categoryId),
sql.Named("name", sub.Name))
if err != nil {
return fmt.Errorf("insert subcategory %s: %w", sub.Name, err)
}
}
if err = tx.Commit(); err != nil {
return fmt.Errorf("commit category: %w", err)
}
return nil
}
Eğer toplu veya depolanan prosedür ile SET XACT_ABORT ONçalışıyorsa, herhangi bir ifade hatasını işlem için terminal olarak kabul eder. Hemen geri alın ve başka komutlar veya Commit() denemeyin. Mevcut sürücü sürümleri, sunucu tarafından sonlandırılan işlemleri algılar ve işlemin bir kısmının sessizce onaylanmasına izin vermek yerine hata döndürür.
Kayıt noktaları
Kayıt puanları, bir işlem içinde ara geri dönüş noktaları oluşturur. SQL Server, kayıt noktalarını doğal olarak destekliyor. Go'nun paketi database/sql kayıt noktalarını doğrudan açığa çıkarmadığından, bunları işlem üzerinden ham SQL olarak çalıştırın:
func createCategoryWithOptionalSubcategory(ctx context.Context, db *sql.DB, category Category) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
err = tx.QueryRowContext(ctx,
"INSERT INTO Production.ProductCategory (Name) OUTPUT INSERTED.ProductCategoryID VALUES (@p1)",
sql.Named("p1", category.Name)).Scan(&category.Id)
if err != nil {
return err
}
// Try to add a subcategory. If it fails, roll back only the subcategory part.
_, err = tx.ExecContext(ctx, "SAVE TRANSACTION AddSubcategory")
if err != nil {
return err
}
_, err = tx.ExecContext(ctx,
"INSERT INTO Production.ProductSubcategory (ProductCategoryID, Name) VALUES (@p1, @p2)",
sql.Named("p1", category.Id),
sql.Named("p2", category.DefaultSubcategory))
if err != nil {
// Roll back only the subcategory; the category insert is preserved.
_, rbErr := tx.ExecContext(ctx, "ROLLBACK TRANSACTION AddSubcategory")
if rbErr != nil {
return fmt.Errorf("rollback savepoint: %w (original: %w)", rbErr, err)
}
log.Printf("Subcategory %q failed, proceeding without it: %v",
category.DefaultSubcategory, err)
}
return tx.Commit()
}
Note
SAVE TRANSACTION <name> kaydetme noktasını oluşturur.
ROLLBACK TRANSACTION <name> dış işlemi sonlandırmadan o geri alma noktasına geri döner.
ROLLBACK isim olmadan tüm işlem geri alınıyor.
Kilitlenme işleme
SQL Server, rakip işlemlerden birini (çıkmaz kurbanı) sonlandırarak ve 1205 hatasını döndürerek çıkmazları çözer. Sonlandırılan işlem sunucu tarafından otomatik olarak geri alınır.
Kilitlenmeleri algıla ve yeniden dene
1205 hatasını kontrol edin ve tüm işlemi kısa bir gecikmeyle tekrar deneyin:
import (
"errors"
"fmt"
"time"
"github.com/microsoft/go-mssqldb"
)
func isDeadlock(err error) bool {
var mssqlErr mssql.Error
return errors.As(err, &mssqlErr) && mssqlErr.Number == 1205
}
func withDeadlockRetry(ctx context.Context, db *sql.DB, maxRetries int,
fn func(ctx context.Context, tx *sql.Tx) error) error {
for attempt := 0; attempt < maxRetries; attempt++ {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
err = fn(ctx, tx)
if err != nil {
tx.Rollback()
if isDeadlock(err) && attempt < maxRetries-1 {
// Wait briefly before retrying.
delay := time.Duration(attempt+1) * 50 * time.Millisecond
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(delay):
}
continue
}
return err
}
if err = tx.Commit(); err != nil {
if isDeadlock(err) && attempt < maxRetries-1 {
delay := time.Duration(attempt+1) * 50 * time.Millisecond
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(delay):
}
continue
}
return err
}
return nil
}
return fmt.Errorf("transaction failed after %d deadlock retries", maxRetries)
}
Deadlock yeniden deneme ambalajını kullanın
Bir işlem fonksiyonunu yeniden deneme wrapper'ına geçirin:
err := withDeadlockRetry(ctx, db, 3, func(ctx context.Context, tx *sql.Tx) error {
_, err := tx.ExecContext(ctx,
"UPDATE Production.ProductInventory SET Quantity = Quantity - @qty WHERE ProductID = @pid AND LocationID = @lid",
sql.Named("qty", orderQty),
sql.Named("pid", productId),
sql.Named("lid", locationId))
return err
})
Kilitlenmeleri azalt
| Strategy | Nasıl yardımcı olur? |
|---|---|
| Tablolara tutarlı bir sırada erişin | Tüm işlemler Tablo A'yı Tablo B'den önce kilitlediğinde, dairesel beklemeler gerçekleşemez. |
| İşlemleri kısa tutun | Daha kısa işlemler kilitleri daha kısa süre tutar ve çatışma penceresini azaltır. |
| En düşük yeterli izolasyon seviyesini kullanın |
ReadCommitteddaha az kilit tutar.Serializable |
| Uygun indeksler ekleyin | Endeks hedefli güncellemeler, tablo taramalarından daha az satır kilitler. |
| İşlemler sırasında kullanıcı etkileşiminden kaçının |
BeginTx ile Commit arasında kullanıcı girdisi beklemeyin. |
Uygulama kodunda tekrar denemek doğru yanıttır, ancak aynı sorguda tekrar tekrar çıkmak bir tasarım sorunu olduğunu gösterir. SQL Server kilitlenme grafiğini (Genişletilmiş Olaylar veya sistem durumu oturumu aracılığıyla yakalanan) kullanarak çakışan deyimleri ve kilit türlerini belirleyin, ardından önceki tablodaki stratejileri uygulayın. Deadlock analizi ve önleme konularında tam bir rehber için Deadlocks rehberine bakınız.
İşlemler ve bağlantı sabitleme
Bir işlem, Commit() veya Rollback() çağrılana kadar havuzdan tek bir bağlantıyı ayırır. Bu süre zarfında başka hiçbir goroutine bu bağlantıyı kullanamaz.
Sonuçlar:
- Uzun süreli işlemler etkili havuz boyutunu azaltır. Eğer 20 açık işleminiz varsa
MaxOpenConns=25, diğer işler için sadece 5 bağlantı mümkündür. - Unutulan
Rollback(), bağlantının kalıcı olarak sızmasına neden olur. - İşlemin bağlamındaki bağlam iptali, işlemi geri alır ve bağlantıyı havuza geri getirir.
// Set a deadline to prevent transactions from running indefinitely.
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
Eşzamanlı işlemler
Her goroutine kendi transaksiyonunu oluşturmalıdır.
*sql.Tx eşzamanlı kullanım için güvenli olmadığından, bir *sql.Tx öğesini asla goroutine’ler arasında paylaşmayın:
// CORRECT: Each goroutine gets its own transaction.
var g errgroup.Group
for _, item := range items {
item := item
g.Go(func() error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
_, err = tx.ExecContext(ctx,
"UPDATE Production.ProductInventory SET Quantity = Quantity - 1 WHERE ProductID = @p1 AND LocationID = @p2",
sql.Named("p1", item.ProductId),
sql.Named("p2", item.LocationId))
if err != nil {
return err
}
return tx.Commit()
})
}
return g.Wait()
Dağıtılmış işlemler
Sürücü, go-mssqldb dağıtık işlemleri (XA işlemleri veya System.Transactions eşdeğerleri) desteklemez. Birden fazla veritabanı arasında çalışmayı koordine etmeniz gerekiyorsa:
- Telafi edici eylemlerle bir saga deseni kullanın.
- Operasyonları mümkün olduğunda tek bir veritabanında birleştirin.
- Her iki veritabanı da SQL Server ise, Transact-SQL (T-SQL) içinden
BEGIN DISTRIBUTED TRANSACTIONile bağlantılı sunucuları kullanın.
İşlem kontrol listesi
| Area | Recommendation |
|---|---|
| Geri dönme güvenliği | Her zaman defer tx.Rollback() hemen sonra BeginTx. |
| Yalıtım düzeyi | (Varsayılan olan) ile ReadCommitted başlayın. Sadece gerektiğinde yükseltme. |
| Deadlocks | İşlemsel kodu bir yeniden deneme döngüsü içine alın. Tablolara tutarlı bir sırayla erişin. |
| Süre | İşlemleri mümkün olduğunca kısa tutun. Bağlam için son tarihler belirleyin. |
| Concurrency |
*sql.Tx öğesini goroutine'ler arasında asla paylaşmayın. |
| Kayıt noktaları | Kısmi geri alma için SAVE TRANSACTION ve ROLLBACK TRANSACTION <name> kullanın. |
| Havuz etkisi | Onaylanmamış işlemlerden kaynaklanan bağlantı sızıntılarını tespit etmek için db.Stats().InUse öğesini izleyin. |