package benchmarks import ( "database/sql" "log" "testing" _ "github.com/lib/pq" ) const ( accountID = "64f2cd7a7047f28fdabf6d9e" connStr = "host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable" queryTotal = ` WITH user_data AS ( SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false ) SELECT f.*, COUNT(*) OVER() as total_count FROM fields f JOIN user_data u ON f.AccountID = u.AmoID WHERE f.Deleted = false ORDER BY f.ID OFFSET ($2 - 1) * $3 LIMIT $3; ` queryCount = ` WITH user_data AS ( SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false ) SELECT COUNT(*) FROM fields f JOIN user_data u ON f.AccountID = u.AmoID WHERE f.Deleted = false; ` queryData = ` WITH user_data AS ( SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false ) SELECT f.* FROM fields f JOIN user_data u ON f.AccountID = u.AmoID WHERE f.Deleted = false ORDER BY f.ID OFFSET ($2 - 1) * $3 LIMIT $3; ` ) type GetFieldsWithPaginationRow struct { ID int64 `db:"id" json:"id"` Amoid int32 `db:"amoid" json:"amoid"` Code string `db:"code" json:"code"` Accountid int32 `db:"accountid" json:"accountid"` Name string `db:"name" json:"name"` Entity interface{} `db:"entity" json:"entity"` Type interface{} `db:"type" json:"type"` Deleted bool `db:"deleted" json:"deleted"` Createdat sql.NullTime `db:"createdat" json:"createdat"` TotalCount int64 `db:"total_count" json:"total_count"` } func initDB() *sql.DB { db, err := sql.Open("postgres", connStr) if err != nil { log.Fatal(err) } return db } // Все получаем в одном запросе не аллоцируя при этом массив func BenchmarkAllOne(b *testing.B) { db := initDB() defer db.Close() for i := 0; i < b.N; i++ { page := 1 size := 25 rows, err := db.Query(queryTotal, accountID, page, size) if err != nil { b.Fatal(err) } defer rows.Close() var results []GetFieldsWithPaginationRow for rows.Next() { var row GetFieldsWithPaginationRow if err := rows.Scan( &row.ID, &row.Amoid, &row.Code, &row.Accountid, &row.Name, &row.Entity, &row.Type, &row.Deleted, &row.Createdat, &row.TotalCount, ); err != nil { b.Fatal(err) } results = append(results, row) } if err := rows.Err(); err != nil { b.Fatal(err) } } } // Все получаем в одном запросе аллоцируя при этом массив func BenchmarkAllOnePreAllocation(b *testing.B) { db := initDB() defer db.Close() for i := 0; i < b.N; i++ { page := 1 size := 25 rows, err := db.Query(queryTotal, accountID, page, size) if err != nil { b.Fatal(err) } defer rows.Close() results := make([]GetFieldsWithPaginationRow, size) for rows.Next() { var row GetFieldsWithPaginationRow if err := rows.Scan( &row.ID, &row.Amoid, &row.Code, &row.Accountid, &row.Name, &row.Entity, &row.Type, &row.Deleted, &row.Createdat, &row.TotalCount, ); err != nil { b.Fatal(err) } results = append(results, row) } if err := rows.Err(); err != nil { b.Fatal(err) } } } // Считается сначала количество потом получаются данные длину и емкость массиву не меняем func BenchmarkCountThenGetData(b *testing.B) { db := initDB() defer db.Close() for i := 0; i < b.N; i++ { page := 1 size := 25 row := db.QueryRow(queryCount, accountID) var totalCount int if err := row.Scan(&totalCount); err != nil { b.Fatal(err) } var results []GetFieldsWithPaginationRow rows, err := db.Query(queryData, accountID, page, size) if err != nil { b.Fatal(err) } defer rows.Close() for rows.Next() { var row GetFieldsWithPaginationRow if err := rows.Scan( &row.ID, &row.Amoid, &row.Code, &row.Accountid, &row.Name, &row.Entity, &row.Type, &row.Deleted, &row.Createdat, ); err != nil { b.Fatal(err) } results = append(results, row) } if err := rows.Err(); err != nil { b.Fatal(err) } } } // Параллельное вычисление данных и общего количество при этом длина слайса = size func BenchmarkParallel(b *testing.B) { db := initDB() defer db.Close() for i := 0; i < b.N; i++ { page := 1 size := 25 results := make([]GetFieldsWithPaginationRow, size) channel := make(chan error, 2) go func() { row := db.QueryRow(queryCount, accountID) var totalCount int channel <- row.Scan(&totalCount) }() go func() { rows, err := db.Query(queryData, accountID, page, size) if err != nil { channel <- err return } defer rows.Close() index := 0 for rows.Next() { if err := rows.Scan( &results[index].ID, &results[index].Amoid, &results[index].Code, &results[index].Accountid, &results[index].Name, &results[index].Entity, &results[index].Type, &results[index].Deleted, &results[index].Createdat, ); err != nil { channel <- err return } index++ } channel <- rows.Err() }() for i := 0; i < 2; i++ { if err := <-channel; err != nil { b.Fatal(err) } } } } // Считается сначала количество потом получаются данные создаем слайс через маке указывая ему длину начальную кап = лен func BenchmarkWithPreAllocation(b *testing.B) { db := initDB() defer db.Close() for i := 0; i < b.N; i++ { page := 1 size := 25 results := make([]GetFieldsWithPaginationRow, size) row := db.QueryRow(queryCount, accountID) var totalCount int if err := row.Scan(&totalCount); err != nil { b.Fatal(err) } rows, err := db.Query(queryData, accountID, page, size) if err != nil { b.Fatal(err) } defer rows.Close() index := 0 for rows.Next() { if err := rows.Scan( &results[index].ID, &results[index].Amoid, &results[index].Code, &results[index].Accountid, &results[index].Name, &results[index].Entity, &results[index].Type, &results[index].Deleted, &results[index].Createdat, ); err != nil { b.Fatal(err) } index++ } if err := rows.Err(); err != nil { b.Fatal(err) } } } func BenchmarkWithPreAllocationAndMonitoringTotalCount(b *testing.B) { db := initDB() defer db.Close() for i := 0; i < b.N; i++ { page := 1 size := 50 row := db.QueryRow(queryCount, accountID) var totalCount int if err := row.Scan(&totalCount); err != nil { b.Fatal(err) } if totalCount < size { size = totalCount } results := make([]GetFieldsWithPaginationRow, size) rows, err := db.Query(queryData, accountID, page, size) if err != nil { b.Fatal(err) } defer rows.Close() index := 0 for rows.Next() { if err := rows.Scan( &results[index].ID, &results[index].Amoid, &results[index].Code, &results[index].Accountid, &results[index].Name, &results[index].Entity, &results[index].Type, &results[index].Deleted, &results[index].Createdat, ); err != nil { b.Fatal(err) } index++ } if err := rows.Err(); err != nil { b.Fatal(err) } } }