Herkese merhaba!

Uzun yıllardır bol miktarda kişisel zaman ve enerji harcayarak bilgimizi hepinizle paylaşıyoruz. Ancak şu andan itibaren bu blogu çalışır durumda tutabilmek için yardımınıza ihtiyacımız var. Yapmanız gereken tek şey, sitedeki reklamlardan birine tıklamak olacaktır, aksi takdirde hosting vb. masraflar nedeniyle maalesef yayından kaldırılacaktır. Teşekkürler.

Bu örnek, CRUD işlemlerini 'yazma' ve 'okuma' olarak iki bölüme ayırır. Yazma bölüme INSERT, UPDATE ve DELETE, okuma ise SELECT sorgularını ilgilendirir. Ayrıca uzun süren sorguları sonlandırmak için context.WithTimeout kullanıyoruz. SELECT sorgularına dikkat edin, çünkü bunlar MAX_EXECUTION_TIME adlı bir özelliğe bağlıdır.


INSERT/UPDATE/DELETE


Hazırlanmış (güvenli seçenek)


Sorgu değişkenlerinin SQL Injection içermediğinden %100 emin değilseniz bunu tercih edin.


Ağ gidiş dönüş sayısını üçe katladığı için yavaş bir işlemdir (Prepare, Execute ve Close). Sorgu, ? bağımsız değişken yer tutucularını kullanır.


Aşağıdaki her iki örnek de aynı şekilde çalışacaktır, bu yüzden daha temiz olduğundan ikincisini kullanmanızı öneririm. Ayrıca, sorgu değişkenleri dinamik veya statik olsada, ağ gidiş dönüş sayısı üç katında kalacaktır.


func Create(args ...interface{}) error {
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()

stmt, err := db.PrepareContext(ctx, `
INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
(?, ?, ?, ?, ?, ?, UTC_TIMESTAMP())
`)
if err != nil {
return err
}
defer stmt.Close()

res, err := stmt.ExecContext(ctx, args...)
if err != nil {
return err
}

tot, err := res.RowsAffected()
if err != nil {
return err
}

if tot != 1 {
return errors.New("no rows were affected")
}

return nil
}

func Create(args ...interface{}) error {
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()

q := `
INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
(?, ?, ?, ?, ?, ?, UTC_TIMESTAMP())
`

res, err := db.ExecContext(ctx, q, args...)
if err != nil {
return err
}

tot, err := res.RowsAffected()
if err != nil {
return err
}

if tot != 1 {
return errors.New("no rows were affected")
}

return nil
}

2020-03-13T13:14:36.035537Z 9 Prepare INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
(?, ?, ?, ?, ?, ?, UTC_TIMESTAMP())

2020-03-13T13:14:36.038073Z 9 Execute INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
('6a980454-0727-4260-a757-ce619e79af83', 'League 1', 'Address', 2, 0, '2001-01-01', UTC_TIMESTAMP())

2020-03-13T13:14:36.052590Z 9 Close stmt

Biçimlendirilmiş (güvensiz seçenek)


Sorgu değişkenlerinin SQL Injection içermediğinden %100 eminseniz bunu tercih edin.


Tek bir ağ gidiş dönüşü (Query) çalıştıracağından hızlı bir işlemdir. Sorgu, uygun değişken biçimlendiricileri kullanılarak fmt.Sprintf işleviyle biçimlendirilir.


func Create(args ...interface{}) error {
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()

q := fmt.Sprintf(`
INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
('%s', '%s', '%s', %d, %t, '%s', UTC_TIMESTAMP())
`,
args...,
)

res, err := db.ExecContext(ctx, q)
if err != nil {
return err
}

tot, err := res.RowsAffected()
if err != nil {
return err
}

if tot != 1 {
return errors.New("no rows were affected")
}

return nil
}

2020-03-13T13:30:26.812083Z 15 Query INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
('2250b0d9-365a-4655-a116-1b092f6d6f20', 'League 1', 'Address', 2, false, '2001-01-01', UTC_TIMESTAMP())

Not


Uzun çalışan sorguları iptal etmek için her ne kadar context.WithTimeout kullanıyorsakta, MySQL protokol yapısı nedeniyle temel sorguları sonlandırmayacaktır. İptal işlemi yalnızca Go düzeyinde gerçekleşir. Ancak, SELECT sorguları için bu sorunu, MySQL sunucusuna verilen süre sınırından daha uzun sürerse, yürütmeyi sonlandırmasını istemek için MySQL'in MAX_EXECUTION_TIME sorgu ipucunu kullanarak çözebilirsiniz. Bu gibi durumlarda Error 3024: Query execution was interrupted, maximum statement execution time exceeded hatası verilir.


SELECT ONE


Hazırlanmış (güvenli seçenek)


Sorgu değişkenlerinin SQL Injection içermediğinden %100 emin değilseniz bunu tercih edin.


Ağ gidiş dönüş sayısını üçe katladığı için yavaş bir işlemdir (Prepare, Execute ve Close). Sorgu, ? bağımsız değişken yer tutucularını kullanır.


Sorgu değişkenleri dinamik veya statik olsada, ağ gidiş dönüş sayısı üç katında kalacaktır.


type League struct {
ID sql.NullInt64
UUID sql.NullString
Name sql.NullString
Address sql.NullString
IntRank sql.NullInt64
IsActive sql.NullBool
FoundedAt mysql.NullTime
CreatedAt mysql.NullTime
DeletedAt mysql.NullTime
}

func Read(uuid string) (League, bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()

row := db.QueryRowContext(ctx, `
SELECT
/*+ MAX_EXECUTION_TIME(3000) */
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
WHERE
uuid = ?
LIMIT 1`,
uuid,
)

var model League

err := row.Scan(
&model.ID,
&model.UUID,
&model.Name,
&model.IntRank,
&model.Address,
&model.IsActive,
&model.FoundedAt,
&model.CreatedAt,
&model.DeletedAt,
)

switch {
case err == sql.ErrNoRows:
return model, false, nil // 404
case err != nil:
return model, false, err // 500
default:
return model, true, nil // 200
}
}

2020-03-13T16:00:11.624465Z 16 Prepare SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
WHERE
uuid = ?
LIMIT 1

2020-03-13T16:00:11.643215Z 16 Execute SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
WHERE
uuid = '2250b0d9-365a-4655-a116-1b092f6d6f20'
LIMIT 1

2020-03-13T16:00:11.674942Z 16 Close stmt

Biçimlendirilmiş (güvensiz seçenek)


Sorgu değişkenlerinin SQL Injection içermediğinden %100 eminseniz bunu tercih edin.


Tek bir ağ gidiş dönüşü (Query) çalıştıracağından hızlı bir işlemdir. Sorgu, uygun değişken biçimlendiricileri kullanılarak fmt.Sprintf işleviyle biçimlendirilir.


func Read(uuid string) (League, bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()

q := fmt.Sprintf(`
SELECT
/*+ MAX_EXECUTION_TIME(3000) */
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
WHERE
uuid = '%s'
LIMIT 1
`,
uuid,
)

row := db.QueryRowContext(ctx, q)

var model League

err := row.Scan(
&model.ID,
&model.UUID,
&model.Name,
&model.IntRank,
&model.Address,
&model.IsActive,
&model.FoundedAt,
&model.CreatedAt,
&model.DeletedAt,
)

switch {
case err == sql.ErrNoRows:
return model, false, nil // 404
case err != nil:
return model, false, err // 500
default:
return model, true, nil // 200
}
}

2020-03-13T16:06:49.526538Z 18 Query SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
WHERE
uuid = '2250b0d9-365a-4655-a116-1b092f6d6f20'
LIMIT 1

SELECT MANY


Hazırlanmış (güvenli seçenek)


Sorgu değişkenlerinin SQL Injection içermediğinden %100 emin değilseniz bunu tercih edin.


Ağ gidiş dönüş sayısını üçe katladığı için yavaş bir işlemdir (Prepare, Execute ve Close). Sorgu, ? bağımsız değişken yer tutucularını kullanır.


Sorgu değişkenleri dinamik veya statik olsada, ağ gidiş dönüş sayısı üç katında kalacaktır.


type League struct {
ID sql.NullInt64
UUID sql.NullString
Name sql.NullString
Address sql.NullString
IntRank sql.NullInt64
IsActive sql.NullBool
FoundedAt mysql.NullTime
CreatedAt mysql.NullTime
DeletedAt mysql.NullTime
}

func Read(limit, offset int) ([]League, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()

rows, err := db.QueryContext(ctx, `
SELECT
/*+ MAX_EXECUTION_TIME(3000) */
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
LIMIT ?
OFFSET ?`,
limit,
offset,
)

var models []League

if err != nil {
return models, err // 500
}
defer rows.Close()

for rows.Next() {
var model League

err := rows.Scan(
&model.ID,
&model.UUID,
&model.Name,
&model.IntRank,
&model.Address,
&model.IsActive,
&model.FoundedAt,
&model.CreatedAt,
&model.DeletedAt,
)
if err != nil {
return models, err // 500
}

models = append(models, model)
}
if err = rows.Err(); err != nil {
return models, err // 500
}

return models, nil
}

2020-03-13T16:35:51.216389Z 22 Prepare SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
LIMIT ?
OFFSET ?

2020-03-13T16:35:51.223332Z 22 Execute SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
LIMIT 10
OFFSET 0

2020-03-13T16:35:51.245423Z 22 Close stmt

Biçimlendirilmiş (güvensiz seçenek)


Sorgu değişkenlerinin SQL Injection içermediğinden %100 eminseniz bunu tercih edin.


Tek bir ağ gidiş dönüşü (Query) çalıştıracağından hızlı bir işlemdir. Sorgu, uygun değişken biçimlendiricileri kullanılarak fmt.Sprintf işleviyle biçimlendirilir.


func Read(limit, offset int) ([]League, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()

q := fmt.Sprintf(`
SELECT
/*+ MAX_EXECUTION_TIME(3000) */
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
LIMIT %d
OFFSET %d
`,
limit,
offset,
)

rows, err := db.QueryContext(ctx, q)

var models []League

if err != nil {
return models, err // 500
}
defer rows.Close()

for rows.Next() {
var model League

err := rows.Scan(
&model.ID,
&model.UUID,
&model.Name,
&model.IntRank,
&model.Address,
&model.IsActive,
&model.FoundedAt,
&model.CreatedAt,
&model.DeletedAt,
)
if err != nil {
return models, err // 500
}

models = append(models, model)
}
if err = rows.Err(); err != nil {
return models, err // 500
}

return models, nil
}

2020-03-13T16:39:42.933817Z 23 Query SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
LIMIT 10
OFFSET 0