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.
Sürücü, go-mssqldb sorguları çalıştırmak ve ifadeleri çalıştırmak için standart database/sql arayüzü kullanır. Bu makale, sürücüyle veri erişimi için yaygın kalıpları ele alır.
SELECT sorgusu çalıştırma
Satır döndüren bir sorguyu çalıştırmak için kullanılır QueryContext :
rows, err := db.QueryContext(ctx,
"SELECT BusinessEntityID, FirstName + ' ' + LastName AS Name, CountryRegionName FROM Sales.vSalesPerson WHERE CountryRegionName = @p1",
sql.Named("p1", "Australia"))
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var id int
var name, location string
if err := rows.Scan(&id, &name, &location); err != nil {
log.Fatal(err)
}
fmt.Printf("%d: %s (%s)\n", id, name, location)
}
if err = rows.Err(); err != nil {
log.Fatal(err)
}
Important
Her zaman rows.Close() öğesini çağırın (genellikle defer ile) ve döngüden sonra rows.Err() öğesini kontrol edin. Sıraları kapatmamak, havuzdan bağlantıların sızmasına neden olabilir.
rows.Close() Ayrıca sürücü kalan tokenları tüketirken sunucu tarafında bir hata döndürebilir, bu yüzden sonuç seti tamamen tüketilmediğinde bunu görmezden gelmeyin.
Erken okumayı bırakırsanız, satırları açıkça kapatın ve kapanış hatasını ele alın:
rows, err := db.QueryContext(ctx,
"SELECT TOP (100) ProductID, Name FROM Production.Product ORDER BY ProductID")
if err != nil {
log.Fatal(err)
}
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
_ = rows.Close()
log.Fatal(err)
}
fmt.Printf("%d %s\n", id, name)
break // Stop early for demonstration.
}
if err := rows.Close(); err != nil {
log.Fatal(err)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
Bu makaledeki örnekler AdventureWorks2025 örnek veritabanıyla karşılaştırılıyor. Okuma odaklı örnekler, , Sales.vSalesPerson, ve Production.Productgibi Sales.SalesOrderHeaderyerleşik nesneleri sorgular. Yazma odaklı örnekler, HumanResources.Department ve Production.ProductInventory için tasarlanmıştır.
Tek bir satırı sorgulama
Tam olarak bir satır beklediğinizde QueryRowContext kullanın:
var id int
var name string
err := db.QueryRowContext(ctx,
"SELECT BusinessEntityID, FirstName + ' ' + LastName AS Name FROM Sales.vSalesPerson WHERE BusinessEntityID = @p1",
sql.Named("p1", 280)).Scan(&id, &name)
if err == sql.ErrNoRows {
fmt.Println("No employee found.")
} else if err != nil {
log.Fatal(err)
} else {
fmt.Printf("Employee %d: %s\n", id, name)
}
Bir komutu çalıştır
ExecContext, INSERT, UPDATE ve DDL deyimleri için DELETE kullanın:
result, err := db.ExecContext(ctx,
"INSERT INTO HumanResources.Department (Name, GroupName) VALUES (@p1, @p2)",
sql.Named("p1", "Data Science"),
sql.Named("p2", "Research and Development"))
if err != nil {
log.Fatal(err)
}
rowsAffected, _ := result.RowsAffected()
fmt.Printf("Rows affected: %d\n", rowsAffected)
Important
go-mssqldb sürücüsü, LastInsertId() öğesini desteklemiyor. Onu çağırmak hata veriyor. Eklenen bir kimlik değerini elde etmek için bir OUTPUT yan tümce veya ayrı bir SELECT SCOPE_IDENTITY() sorgu kullanın.
SELECT SCOPE_IDENTITY() kullanıyorsanız, kimlik kapsamının aynı bağlantıda kalması için onu INSERT ile aynı toplu işte veya işlemde çalıştırın.
Bir saklı yordam veya tetikleyici SET NOCOUNT ON kullanıyorsa, SQL Server satır sayısı iletisini gizlediği için RowsAffected() 0 döndürür. Gerçek sayıya ihtiyacınız varsa, ya prosedürden çıkarın SET NOCOUNT ON ya da sayıyı bir çıkış parametresi veya SELECT ifadesi aracılığıyla açıkça geri döndürün.
Parametreli sorgular
SQL enjeksiyonundan kaçınmak için her zaman parametrizlenmiş sorgular kullanın. Sürücü hem konumsal hem de adlandırılmış parametreleri destekler.
Important
go-mssqldb sürücüsü, konumsal parametreler için @p1, @p2 ve benzerlerini, adlandırılmış parametreler içinse sql.Named() kullanır.
? Bazı diğer sürücülerin (örneğin MySQL'ingo-sql-driver) kullandığı yer tutucu sözdizimi sürücü sqlserver adıyla çalışmaz. Başka bir veritabanından taşınıyorsanız, tüm ? veya $1-style yer tutucularını @p1-style veya adlandırılmış parametrelerle değiştirin.
Konumsal parametreler
@p1 ve @p2 yer tutucularını kullanın ve değerleri sırayla iletin:
rows, err := db.QueryContext(ctx,
"SELECT BusinessEntityID, FirstName, CountryRegionName FROM Sales.vSalesPerson WHERE FirstName = @p1 AND CountryRegionName = @p2",
"Jared", "Australia")
Adlandırılmış parametreler
Değerleri adlandırılmış yer tutuculara bağlamak için kullanılır sql.Named() :
rows, err := db.QueryContext(ctx,
"SELECT BusinessEntityID, FirstName, CountryRegionName FROM Sales.vSalesPerson WHERE FirstName = @name AND CountryRegionName = @location",
sql.Named("name", "Jared"),
sql.Named("location", "Australia"))
Birden fazla sonuç kümesi
rows.NextResultSet(), tek bir işlem grubu veya saklı yordamın döndürdüğü birden fazla sonuç kümesinde yineleme yapmak için kullanılır.
Important
rows.Next() çağrısından önce her sonuç kümesi için rows.NextResultSet() işlemini tamamen tüketmeniz gerekir.
NextResultSet() öğesinden önce Next() çağrılması, false döndürür ve kalan satırları sessizce atlar.
Tüm sonuç kümelerini güvenilir şekilde işlemek için bu döngü desenini kullanın:
rows, err := db.QueryContext(ctx,
`SELECT TOP (3) ProductID, Name
FROM Production.Product
ORDER BY ProductID;
SELECT TOP (3) SalesOrderID, CONVERT(NVARCHAR(10), OrderDate, 23) AS OrderDate
FROM Sales.SalesOrderHeader
ORDER BY SalesOrderID DESC;`)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
setIndex := 0
for {
switch setIndex {
case 0:
for rows.Next() {
var productID int
var productName string
if err := rows.Scan(&productID, &productName); err != nil {
log.Fatal(err)
}
fmt.Printf("Product %d: %s\n", productID, productName)
}
case 1:
for rows.Next() {
var salesOrderID int
var orderDate string
if err := rows.Scan(&salesOrderID, &orderDate); err != nil {
log.Fatal(err)
}
fmt.Printf("Order %d: %s\n", salesOrderID, orderDate)
}
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
if !rows.NextResultSet() {
break
}
setIndex++
}
Transactions
Belirli bir izolasyon seviyesinde işlem başlatmak için kullanılır BeginTx . İzolasyon seviyeleri, kayıt noktaları, deadlock yönetimi ve yeniden deneme kalıpları dahil olmak üzere kapsamlı işlem rehberliği için Transactions'a bakınız.
tx, err := db.BeginTx(ctx, &sql.TxOptions{
Isolation: sql.LevelSerializable,
})
if err != nil {
log.Fatal(err)
}
defer tx.Rollback()
// Subtract from source location.
_, err = tx.ExecContext(ctx,
"UPDATE Production.ProductInventory SET Quantity = Quantity - @p1 WHERE ProductID = @p2 AND LocationID = 1",
sql.Named("p1", 5),
sql.Named("p2", 1))
if err != nil {
log.Fatal(err)
}
// Add to destination location.
_, err = tx.ExecContext(ctx,
"UPDATE Production.ProductInventory SET Quantity = Quantity + @p1 WHERE ProductID = @p2 AND LocationID = 6",
sql.Named("p1", 5),
sql.Named("p2", 1))
if err != nil {
log.Fatal(err)
}
if err = tx.Commit(); err != nil {
log.Fatal(err)
}
Eklenen kimlik değerlerini alın
go-mssqldb sürücüsü, LastInsertId() öğesini desteklemiyor. Aynı ifade içinde kimlik değerini almak için OUTPUT yan tümcesini kullanın:
var newID int64
err := db.QueryRowContext(ctx,
"INSERT INTO HumanResources.Department (Name, GroupName) OUTPUT INSERTED.DepartmentID VALUES (@name, @grp)",
sql.Named("name", "Data Science"),
sql.Named("grp", "Research and Development")).Scan(&newID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Inserted department with ID: %d\n", newID)
Birden fazla satır için:
rows, err := db.QueryContext(ctx, `
INSERT INTO HumanResources.Department (Name, GroupName)
OUTPUT INSERTED.DepartmentID, INSERTED.Name
VALUES (@n1, @g1), (@n2, @g2)`,
sql.Named("n1", "Data Science"), sql.Named("g1", "Research and Development"),
sql.Named("n2", "Cloud Ops"), sql.Named("g2", "Information Technology"))
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var id int64
var name string
if err := rows.Scan(&id, &name); err != nil {
log.Fatal(err)
}
fmt.Printf("Inserted: %d - %s\n", id, name)
}
Pagination
Sunucu taraflı sayfalama için OFFSET ve FETCH NEXT kullanın. Bir ORDER BY madde gereklidir:
Ofset tabanlı sayfalama
Ofset ve sayfa boyutunu parametre olarak verin:
func getEmployeesPage(ctx context.Context, db *sql.DB, page, pageSize int) ([]Employee, error) {
offset := (page - 1) * pageSize
rows, err := db.QueryContext(ctx, `
SELECT BusinessEntityID, FirstName + ' ' + LastName AS Name, CountryRegionName AS Location
FROM Sales.vSalesPerson
ORDER BY BusinessEntityID
OFFSET @offset ROWS
FETCH NEXT @pageSize ROWS ONLY`,
sql.Named("offset", offset),
sql.Named("pageSize", pageSize))
if err != nil {
return nil, err
}
defer rows.Close()
var employees []Employee
for rows.Next() {
var e Employee
if err := rows.Scan(&e.Id, &e.Name, &e.Location); err != nil {
return nil, err
}
employees = append(employees, e)
}
return employees, rows.Err()
}
Büyük tablolar için anahtar kümesiyle sayfalama
Büyük tablolarda ofset sayfalandırma yavaşlar çünkü sunucu satır atlamak zorundadır. Anahtar seti sayfalaması, bir sonraki sayfayı verimli bir şekilde almak için son görülen anahtarı kullanır:
func getNextPage(ctx context.Context, db *sql.DB, lastID int, pageSize int) ([]Employee, error) {
rows, err := db.QueryContext(ctx, `
SELECT TOP(@pageSize) BusinessEntityID, FirstName + ' ' + LastName AS Name, CountryRegionName AS Location
FROM Sales.vSalesPerson
WHERE BusinessEntityID > @lastID
ORDER BY BusinessEntityID`,
sql.Named("pageSize", pageSize),
sql.Named("lastID", lastID))
if err != nil {
return nil, err
}
defer rows.Close()
var employees []Employee
for rows.Next() {
var e Employee
if err := rows.Scan(&e.Id, &e.Name, &e.Location); err != nil {
return nil, err
}
employees = append(employees, e)
}
return employees, rows.Err()
}
İpucu
Anahtar seti sayfalaması, derin sayfalara (sayfa 1000+) göre OFFSET/FETCH önemli ölçüde daha hızlıdır çünkü satır tarama ve atlama yerine indeks araması kullanır.
Birden çok ifadeyi toplu işleme
Ağ gidiş-dönüş mesafelerini azaltmak için tek bir çağrıda birden fazla SQL ifadesi gönderin.
rows, err := db.QueryContext(ctx, `
SELECT COUNT(*) FROM HumanResources.Employee;
SELECT COUNT(*) FROM Sales.SalesOrderHeader;
SELECT COUNT(*) FROM Production.Product;`)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
var empCount, orderCount, productCount int
if rows.Next() {
if err := rows.Scan(&empCount); err != nil {
log.Fatal(err)
}
}
if rows.NextResultSet() && rows.Next() {
if err := rows.Scan(&orderCount); err != nil {
log.Fatal(err)
}
}
if rows.NextResultSet() && rows.Next() {
if err := rows.Scan(&productCount); err != nil {
log.Fatal(err)
}
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
fmt.Printf("Employees: %d, Orders: %d, Products: %d\n",
empCount, orderCount, productCount)
Büyük sonuç kümelerini verimli şekilde işleyin
Milyonlarca satır döndüren sorgular için sonuçları akış biçiminde işleyin. Tüm satırları hafızada biriktirme.
func processLargeTable(ctx context.Context, db *sql.DB) error {
rows, err := db.QueryContext(ctx, "SELECT TransactionID, CONVERT(NVARCHAR(30), TransactionDate, 126) FROM Production.TransactionHistory")
if err != nil {
return err
}
defer rows.Close()
var processed int
for rows.Next() {
var id int
var data string
if err := rows.Scan(&id, &data); err != nil {
return err
}
// Process each row without accumulating.
if err := handleRow(id, data); err != nil {
return err
}
processed++
if processed%10000 == 0 {
log.Printf("Processed %d rows", processed)
}
}
return rows.Err()
}
Dikkat
Açık *sql.Rows bir bağlantı, havuzdan çağrılana rows.Close() kadar bir bağlantı açar. Sonuç kümesinin çok uzun süren işlenmesi durumunda, bağlantıyı dakikalar boyunca açık tutmamak için işi anahtar tabanlı sayfalama kullanarak aralıklara bölmeyi değerlendirin.
Upsert ile MERGE
SQL Server, insert veya update (upsert) işlemleri için bu MERGE ifadeyi kullanır.
_, err := db.ExecContext(ctx, `
MERGE HumanResources.Department AS target
USING (SELECT @id AS DepartmentID, @name AS Name, @grp AS GroupName) AS source
ON target.DepartmentID = source.DepartmentID
WHEN MATCHED THEN
UPDATE SET Name = source.Name, GroupName = source.GroupName
WHEN NOT MATCHED THEN
INSERT (Name, GroupName)
VALUES (source.Name, source.GroupName);`,
sql.Named("id", dept.Id),
sql.Named("name", dept.Name),
sql.Named("grp", dept.GroupName))
Hazırlanmış ifadeler
Yeniden kullanılabilir hazırlanmış bir ifade oluşturmak için kullanın PrepareContext . Hazırlanmış ifadeler, aynı sorgu farklı parametrelerle birçok kez çalıştığında performansı artırabilir.
stmt, err := db.PrepareContext(ctx,
"SELECT TOP (1) FirstName + ' ' + LastName AS Name FROM Sales.vSalesPerson WHERE CountryRegionName = @p1")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
for _, location := range []string{"Australia", "India", "Germany"} {
var name string
err := stmt.QueryRowContext(ctx, location).Scan(&name)
if err != nil {
log.Println(location, err)
continue
}
fmt.Printf("%s: %s\n", location, name)
}
Bağlam iptal
Tüm database/sql yöntemleri, bir context.Context kabul eder. Mola ve iptal için kullanın.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, "SELECT * FROM Production.TransactionHistory")
Bağlamın zaman aşımı süresi dolarsa, sürücü sunucudaki sorguyu iptal eder ve çağırana bir hata döndürür.