Encoding Csv #

CSV (Comma-Separated Values) adalah format pertukaran data yang paling universal — hampir setiap sistem bisa mengekspor dan mengimpor CSV, dari spreadsheet Excel hingga database PostgreSQL. Tapi CSV memiliki banyak edge case yang mudah diabaikan: field yang mengandung koma harus dikutip, kutipan dalam field harus di-escape, newline di dalam field juga valid, dan delimiter bisa bukan koma (tab, titik koma, pipe). Package encoding/csv menangani semua kerumitan ini dengan benar sesuai RFC 4180. Artikel ini membahas cara membaca dan menulis CSV dengan tepat, menangani edge case, mengkonfigurasi reader dan writer, serta pola pemrosesan CSV di aplikasi produksi.

Gambaran Besar Package encoding/csv #

flowchart LR
    subgraph Read["Membaca CSV"]
        R1["csv.NewReader(r)"] --> R2["reader.Read()\nsatu baris → []string"]
        R1 --> R3["reader.ReadAll()\nsemua baris → [][]string"]
        R2 --> R4["loop sampai io.EOF"]
    end

    subgraph Write["Menulis CSV"]
        W1["csv.NewWriter(w)"] --> W2["writer.Write([]string)\ntulis satu baris"]
        W1 --> W3["writer.WriteAll([][]string)\ntulis semua baris"]
        W2 --> W4["writer.Flush()\nwajib dipanggil!"]
        W3 --> W5["writer.Error()\nperiksa error setelah Flush"]
    end

    subgraph Config["Konfigurasi"]
        C1["reader.Comma = ';'\ndelimiter kustom"]
        C2["reader.Comment = '#'\nskip baris komentar"]
        C3["reader.FieldsPerRecord\nvalidasi jumlah field"]
        C4["reader.LazyQuotes = true\ntolerasi kutip tidak standar"]
        C5["reader.TrimLeadingSpace\nhapus spasi di awal"]
        C6["writer.Comma = '\\t'\ntulis TSV"]
        C7["writer.UseCRLF\ngunakan \\r\\n"]
    end

    style Read fill:#e8f5e9
    style Write fill:#e3f2fd
    style Config fill:#fff3e0

Membaca CSV — csv.Reader #

Membaca Baris per Baris #

package main

import (
    "encoding/csv"
    "fmt"
    "io"
    "os"
    "strings"
)

func main() {
    input := `nama,email,kota
Budi Santoso,[email protected],Jakarta
Ani Wijaya,[email protected],Bandung
Charlie,[email protected],"Surabaya, Jawa Timur"
`
    reader := csv.NewReader(strings.NewReader(input))

    for {
        record, err := reader.Read()

        // EOF berarti selesai — bukan error sebenarnya
        if err == io.EOF {
            break
        }
        if err != nil {
            fmt.Fprintf(os.Stderr, "error membaca: %v\n", err)
            return
        }

        // record adalah []string — satu elemen per field
        fmt.Println(record)
    }
    // [nama email kota]
    // [Budi Santoso [email protected] Jakarta]
    // [Ani Wijaya [email protected] Bandung]
    // [Charlie [email protected] Surabaya, Jawa Timur]  ← tanda kutip sudah dihapus!
}

ReadAll — Baca Semua Sekaligus #

func bacaCSVSederhana(path string) ([][]string, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, fmt.Errorf("bacaCSVSederhana: %w", err)
    }
    defer f.Close()

    reader := csv.NewReader(f)

    // ReadAll — baca semua baris sekaligus
    // Cocok untuk file kecil yang muat di memori
    records, err := reader.ReadAll()
    if err != nil {
        return nil, fmt.Errorf("bacaCSVSederhana ReadAll: %w", err)
    }

    return records, nil
}

// Penggunaan
records, err := bacaCSVSederhana("data.csv")
if err != nil {
    log.Fatal(err)
}

// records[0] adalah header
header := records[0]
fmt.Println("Kolom:", header)

// records[1:] adalah data
for _, row := range records[1:] {
    fmt.Println(row)
}

Konfigurasi csv.Reader #

flowchart TD
    Reader["csv.NewReader(r)"] --> Config["Konfigurasi sebelum Read()"]

    Config --> Comma["reader.Comma\ndefault: ',' (koma)\nbisa diubah ke ';' '\\t' '|' dll"]
    Config --> Comment["reader.Comment\ndefault: 0 (tidak ada)\ncontoh: '#' untuk skip komentar"]
    Config --> Fields["reader.FieldsPerRecord\ndefault: 0 (auto dari baris pertama)\n-1: fleksibel (tidak divalidasi)\nN: harus tepat N field per baris"]
    Config --> Lazy["reader.LazyQuotes\ndefault: false\ntrue: tolerasi kutip tidak standar"]
    Config --> Trim["reader.TrimLeadingSpace\ndefault: false\ntrue: hapus spasi di awal field"]
    Config --> ReuseRecord["reader.ReuseRecord\ndefault: false\ntrue: reuse slice (lebih cepat,\ntapi isi sebelumnya dioverwrite)"]

    style Reader fill:#4f86c6,color:#fff
    style Config fill:#e8f5e9
// TSV (Tab-Separated Values)
readerTSV := csv.NewReader(r)
readerTSV.Comma = '\t'

// CSV dengan delimiter titik koma (umum di Eropa)
readerSemicolon := csv.NewReader(r)
readerSemicolon.Comma = ';'

// CSV dengan komentar
readerWithComment := csv.NewReader(r)
readerWithComment.Comment = '#'

// Input:
// # ini komentar, diabaikan
// nama,nilai
// Budi,90

// Validasi jumlah field
readerStrict := csv.NewReader(r)
readerStrict.FieldsPerRecord = 3 // HARUS tepat 3 field per baris
// Error jika ada baris dengan jumlah field yang berbeda

readerFlexible := csv.NewReader(r)
readerFlexible.FieldsPerRecord = -1 // terima berapa saja field per baris

// LazyQuotes — untuk CSV dari sistem lain yang tidak 100% sesuai RFC 4180
// Contoh: field "Jakarta" Barat (kutip tidak ditutup dengan benar)
readerLazy := csv.NewReader(r)
readerLazy.LazyQuotes = true

// TrimLeadingSpace — berguna untuk CSV yang punya spasi setelah koma
// Contoh: nama, email, kota  (ada spasi setelah koma)
readerTrim := csv.NewReader(r)
readerTrim.TrimLeadingSpace = true

// ReuseRecord — performa lebih baik jika kamu menyalin data sebelum iterasi berikutnya
readerFast := csv.NewReader(r)
readerFast.ReuseRecord = true // HATI-HATI: record sebelumnya dioverwrite!

for {
    record, err := readerFast.Read()
    if err == io.EOF {
        break
    }
    // WAJIB: salin record sebelum iterasi berikutnya jika ingin disimpan
    salinan := make([]string, len(record))
    copy(salinan, record)
    // ANTI-PATTERN: simpan record langsung tanpa copy
    // data = append(data, record) — isi data akan ter-overwrite di iterasi berikutnya!
}

func tulisCSV(path string, headers []string, rows [][]string) error {
    f, err := os.Create(path)
    if err != nil {
        return fmt.Errorf("tulisCSV create: %w", err)
    }
    defer f.Close()

    writer := csv.NewWriter(f)

    // Tulis header
    if err := writer.Write(headers); err != nil {
        return fmt.Errorf("tulisCSV tulis header: %w", err)
    }

    // Tulis baris data
    for _, row := range rows {
        if err := writer.Write(row); err != nil {
            return fmt.Errorf("tulisCSV tulis baris: %w", err)
        }
    }

    // WAJIB: Flush memindahkan data dari buffer ke file
    writer.Flush()

    // Periksa error setelah Flush
    if err := writer.Error(); err != nil {
        return fmt.Errorf("tulisCSV flush: %w", err)
    }

    return nil
}

// WriteAll — tulis semua sekaligus
func tulisCSVSekaligus(path string, records [][]string) error {
    f, err := os.Create(path)
    if err != nil {
        return fmt.Errorf("tulisCSVSekaligus: %w", err)
    }
    defer f.Close()

    writer := csv.NewWriter(f)
    if err := writer.WriteAll(records); err != nil {
        return fmt.Errorf("tulisCSVSekaligus WriteAll: %w", err)
    }

    // WriteAll otomatis flush, tapi tetap periksa error
    return writer.Error()
}
Selalu panggil writer.Flush() dan periksa writer.Error() setelah selesai menulis. csv.Writer menggunakan bufio.Writer di dalamnya — data yang belum di-flush akan hilang saat file ditutup tanpa error apapun. WriteAll sudah memanggil Flush secara internal, tapi tetap periksa writer.Error() setelahnya.

Konfigurasi csv.Writer #

// TSV — Tab-Separated Values
writer := csv.NewWriter(f)
writer.Comma = '\t'

// CSV dengan delimiter titik koma
writer.Comma = ';'

// Gunakan CRLF (Windows-style line ending)
writer.UseCRLF = true

// Penulisan ke http.ResponseWriter untuk download
func handlerDownloadCSV(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/csv")
    w.Header().Set("Content-Disposition", `attachment; filename="data.csv"`)

    writer := csv.NewWriter(w)
    writer.UseCRLF = true // Excel di Windows butuh CRLF

    // Tulis header
    writer.Write([]string{"ID", "Nama", "Email", "Kota"})

    // Tulis data dari database
    rows, _ := db.Query("SELECT id, nama, email, kota FROM pengguna")
    defer rows.Close()

    for rows.Next() {
        var id int
        var nama, email, kota string
        rows.Scan(&id, &nama, &email, &kota)
        writer.Write([]string{
            strconv.Itoa(id),
            nama,
            email,
            kota,
        })
    }

    writer.Flush()
}

Menangani Header — Mapping ke Struct #

Package encoding/csv tidak secara langsung mendukung mapping ke struct, tapi pola ini mudah diimplementasikan:

sequenceDiagram
    participant File as CSV File
    participant Reader as csv.Reader
    participant Code as Kode Go
    participant Struct as []Produk

    File->>Reader: baca
    Reader->>Code: record[0] = header row\n["id","nama","harga","stok"]
    Code->>Code: buat map: header → indeks\n{"id":0,"nama":1,"harga":2,"stok":3}
    loop setiap baris data
        Reader->>Code: record[n] = data row\n["1","Laptop","15000000","10"]
        Code->>Struct: Produk{\n  ID: record[idx["id"]],\n  Nama: record[idx["nama"]],\n  ...\n}
    end
type Produk struct {
    ID       int
    Nama     string
    Harga    float64
    Stok     int
    Kategori string
}

func bacaProdukDariCSV(path string) ([]Produk, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, fmt.Errorf("bacaProdukDariCSV: %w", err)
    }
    defer f.Close()

    reader := csv.NewReader(f)
    reader.TrimLeadingSpace = true

    // Baca header
    header, err := reader.Read()
    if err != nil {
        return nil, fmt.Errorf("baca header: %w", err)
    }

    // Buat map header → indeks
    idx := make(map[string]int)
    for i, h := range header {
        idx[strings.ToLower(strings.TrimSpace(h))] = i
    }

    // Validasi kolom yang diperlukan
    required := []string{"id", "nama", "harga", "stok"}
    for _, col := range required {
        if _, ada := idx[col]; !ada {
            return nil, fmt.Errorf("kolom '%s' tidak ditemukan di CSV", col)
        }
    }

    var produk []Produk
    nomorBaris := 1

    for {
        record, err := reader.Read()
        if err == io.EOF {
            break
        }
        if err != nil {
            return nil, fmt.Errorf("baca baris %d: %w", nomorBaris, err)
        }
        nomorBaris++

        // Parse setiap field dengan validasi
        id, err := strconv.Atoi(strings.TrimSpace(record[idx["id"]]))
        if err != nil {
            return nil, fmt.Errorf("baris %d: ID tidak valid %q: %w",
                nomorBaris, record[idx["id"]], err)
        }

        harga, err := strconv.ParseFloat(
            strings.ReplaceAll(record[idx["harga"]], ",", ""), 64)
        if err != nil {
            return nil, fmt.Errorf("baris %d: harga tidak valid %q: %w",
                nomorBaris, record[idx["harga"]], err)
        }

        stok, err := strconv.Atoi(strings.TrimSpace(record[idx["stok"]]))
        if err != nil {
            return nil, fmt.Errorf("baris %d: stok tidak valid %q: %w",
                nomorBaris, record[idx["stok"]], err)
        }

        p := Produk{
            ID:    id,
            Nama:  strings.TrimSpace(record[idx["nama"]]),
            Harga: harga,
            Stok:  stok,
        }

        // Kolom opsional
        if i, ada := idx["kategori"]; ada && i < len(record) {
            p.Kategori = strings.TrimSpace(record[i])
        }

        produk = append(produk, p)
    }

    return produk, nil
}
func tulisProdukKeCSV(path string, produk []Produk) error {
    f, err := os.Create(path)
    if err != nil {
        return fmt.Errorf("tulisProdukKeCSV: %w", err)
    }
    defer f.Close()

    writer := csv.NewWriter(f)

    // Tulis header
    if err := writer.Write([]string{"id", "nama", "harga", "stok", "kategori"}); err != nil {
        return fmt.Errorf("tulis header: %w", err)
    }

    // Tulis setiap produk
    for _, p := range produk {
        record := []string{
            strconv.Itoa(p.ID),
            p.Nama,
            strconv.FormatFloat(p.Harga, 'f', 2, 64),
            strconv.Itoa(p.Stok),
            p.Kategori,
        }
        if err := writer.Write(record); err != nil {
            return fmt.Errorf("tulis produk %d: %w", p.ID, err)
        }
    }

    writer.Flush()
    return writer.Error()
}

Edge Case yang Perlu Diperhatikan #

Field dengan Koma, Kutip, dan Newline #

// csv.Writer menangani semua edge case secara otomatis!
writer := csv.NewWriter(os.Stdout)

// Field dengan koma — otomatis dikutip
writer.Write([]string{"Surabaya, Jawa Timur", "60000"})
// Output: "Surabaya, Jawa Timur",60000

// Field dengan tanda kutip — otomatis di-escape dengan kutip ganda
writer.Write([]string{`Produk "Premium"`, "15000"})
// Output: "Produk ""Premium""",15000

// Field dengan newline — otomatis dikutip
writer.Write([]string{"Deskripsi\nbaris kedua", "active"})
// Output: "Deskripsi
// baris kedua",active

// Field kosong
writer.Write([]string{"", "nilai", ""})
// Output: ,nilai,

writer.Flush()

Mendeteksi dan Menangani Error CSV #

func bacaCSVDenganErrorHandling(r io.Reader) ([][]string, error) {
    reader := csv.NewReader(r)
    reader.LazyQuotes = true // toleran terhadap CSV yang tidak sempurna

    var records [][]string
    var parseErrors []string
    nomorBaris := 0

    for {
        nomorBaris++
        record, err := reader.Read()

        if err == io.EOF {
            break
        }

        if err != nil {
            // Periksa jenis error CSV
            var csvErr *csv.ParseError
            if errors.As(err, &csvErr) {
                // ParseError menyertakan informasi baris dan kolom
                parseErrors = append(parseErrors,
                    fmt.Sprintf("baris %d, kolom %d: %v",
                        csvErr.Line, csvErr.Column, csvErr.Err))
                continue // lanjutkan ke baris berikutnya
            }
            // Error I/O yang serius — hentikan
            return nil, fmt.Errorf("baris %d: %w", nomorBaris, err)
        }

        records = append(records, record)
    }

    if len(parseErrors) > 0 {
        fmt.Fprintf(os.Stderr, "Peringatan: %d baris dilewati karena error:\n",
            len(parseErrors))
        for _, e := range parseErrors {
            fmt.Fprintf(os.Stderr, "  - %s\n", e)
        }
    }

    return records, nil
}

Pola Penggunaan di Produksi #

Pipeline: Baca → Transformasi → Tulis #

// Transformasi CSV: filter, ubah format, tambah kolom
func transformasiCSV(src io.Reader, dst io.Writer, minHarga float64) error {
    reader := csv.NewReader(src)
    reader.TrimLeadingSpace = true

    writer := csv.NewWriter(dst)
    defer writer.Flush()

    // Baca dan teruskan header dengan kolom tambahan
    header, err := reader.Read()
    if err != nil {
        return fmt.Errorf("baca header: %w", err)
    }

    // Tambahkan kolom "kategori_harga"
    headerBaru := append(header, "kategori_harga")
    if err := writer.Write(headerBaru); err != nil {
        return err
    }

    // Cari indeks kolom harga
    idxHarga := -1
    for i, h := range header {
        if strings.EqualFold(h, "harga") {
            idxHarga = i
            break
        }
    }
    if idxHarga < 0 {
        return fmt.Errorf("kolom 'harga' tidak ditemukan")
    }

    nomorBaris := 1
    diproses, dilewati := 0, 0

    for {
        record, err := reader.Read()
        if err == io.EOF {
            break
        }
        if err != nil {
            nomorBaris++
            dilewati++
            fmt.Fprintf(os.Stderr, "skip baris %d: %v\n", nomorBaris, err)
            continue
        }
        nomorBaris++

        // Parse harga
        harga, err := strconv.ParseFloat(
            strings.ReplaceAll(record[idxHarga], ",", ""), 64)
        if err != nil {
            dilewati++
            continue
        }

        // Filter: skip produk di bawah harga minimum
        if harga < minHarga {
            dilewati++
            continue
        }

        // Tambahkan kolom kategori harga
        var kategori string
        switch {
        case harga >= 10000000:
            kategori = "premium"
        case harga >= 1000000:
            kategori = "menengah"
        default:
            kategori = "ekonomis"
        }

        recordBaru := append(record, kategori)
        if err := writer.Write(recordBaru); err != nil {
            return fmt.Errorf("tulis baris %d: %w", nomorBaris, err)
        }
        diproses++
    }

    fmt.Fprintf(os.Stderr, "Selesai: %d diproses, %d dilewati\n",
        diproses, dilewati)

    return writer.Error()
}

Import CSV ke Database #

func importCSVKeBD(path string, db *sql.DB) error {
    f, err := os.Open(path)
    if err != nil {
        return fmt.Errorf("buka file: %w", err)
    }
    defer f.Close()

    reader := csv.NewReader(f)
    reader.TrimLeadingSpace = true

    // Baca header
    header, err := reader.Read()
    if err != nil {
        return fmt.Errorf("baca header: %w", err)
    }

    // Validasi header
    required := map[string]bool{"nama": false, "email": false, "kota": false}
    idx := make(map[string]int)
    for i, h := range header {
        key := strings.ToLower(strings.TrimSpace(h))
        idx[key] = i
        if _, ada := required[key]; ada {
            required[key] = true
        }
    }
    for col, ada := range required {
        if !ada {
            return fmt.Errorf("kolom wajib '%s' tidak ada", col)
        }
    }

    // Mulai transaksi database
    tx, err := db.Begin()
    if err != nil {
        return fmt.Errorf("mulai transaksi: %w", err)
    }
    defer tx.Rollback() // akan di-rollback jika Commit tidak dipanggil

    stmt, err := tx.Prepare(
        "INSERT INTO pengguna (nama, email, kota) VALUES ($1, $2, $3) " +
        "ON CONFLICT (email) DO UPDATE SET nama=$1, kota=$3")
    if err != nil {
        return fmt.Errorf("prepare statement: %w", err)
    }
    defer stmt.Close()

    berhasil, gagal := 0, 0
    nomorBaris := 1

    for {
        record, err := reader.Read()
        if err == io.EOF {
            break
        }
        nomorBaris++
        if err != nil {
            gagal++
            fmt.Fprintf(os.Stderr, "skip baris %d: %v\n", nomorBaris, err)
            continue
        }

        nama := strings.TrimSpace(record[idx["nama"]])
        email := strings.TrimSpace(record[idx["email"]])
        kota := strings.TrimSpace(record[idx["kota"]])

        if nama == "" || email == "" {
            gagal++
            continue
        }

        if _, err := stmt.Exec(nama, email, kota); err != nil {
            fmt.Fprintf(os.Stderr, "baris %d gagal insert: %v\n", nomorBaris, err)
            gagal++
            continue
        }
        berhasil++

        // Commit setiap 1000 baris untuk menghindari transaksi yang terlalu besar
        if berhasil%1000 == 0 {
            if err := tx.Commit(); err != nil {
                return fmt.Errorf("commit: %w", err)
            }
            tx, _ = db.Begin()
            stmt, _ = tx.Prepare(
                "INSERT INTO pengguna (nama, email, kota) VALUES ($1, $2, $3) " +
                "ON CONFLICT (email) DO UPDATE SET nama=$1, kota=$3")
            fmt.Fprintf(os.Stderr, "Progress: %d berhasil\n", berhasil)
        }
    }

    if err := tx.Commit(); err != nil {
        return fmt.Errorf("commit akhir: %w", err)
    }

    fmt.Printf("Import selesai: %d berhasil, %d gagal\n", berhasil, gagal)
    return nil
}

Export dari Database ke CSV #

func eksporPenggunakeCSV(db *sql.DB, w io.Writer) error {
    rows, err := db.Query(
        "SELECT id, nama, email, kota, created_at FROM pengguna ORDER BY id")
    if err != nil {
        return fmt.Errorf("query: %w", err)
    }
    defer rows.Close()

    writer := csv.NewWriter(w)

    // Tulis header
    writer.Write([]string{"id", "nama", "email", "kota", "tanggal_daftar"})

    for rows.Next() {
        var id int
        var nama, email, kota string
        var createdAt time.Time

        if err := rows.Scan(&id, &nama, &email, &kota, &createdAt); err != nil {
            fmt.Fprintf(os.Stderr, "scan error: %v\n", err)
            continue
        }

        writer.Write([]string{
            strconv.Itoa(id),
            nama,
            email,
            kota,
            createdAt.Format("2006-01-02"),
        })
    }

    writer.Flush()

    if err := rows.Err(); err != nil {
        return fmt.Errorf("iterasi rows: %w", err)
    }

    return writer.Error()
}

Kapan Beralih ke Alternatif #

Tetap gunakan encoding/csv jika:
  ✓ Baca dan tulis CSV standar (RFC 4180)
  ✓ CSV dari Excel, Google Sheets, atau sistem lain yang umum
  ✓ CSV sederhana dengan delimiter koma atau tab
  ✓ File CSV kecil hingga menengah
  ✓ Import/export data ke database

Pertimbangkan parsing manual dengan bufio.Scanner jika:
  ✗ CSV sangat sederhana tanpa quoting sama sekali
  ✗ Format tidak sesuai RFC 4180 dan LazyQuotes tidak cukup
  ✗ Butuh kontrol penuh atas parsing setiap karakter

Pertimbangkan library eksternal jika:
  ✗ CSV dengan jutaan baris → gocsv untuk mapping struct otomatis
  ✗ Infer tipe kolom otomatis (int, float, bool, date)
     → csvutil, gocsv
  ✗ Schema validation per kolom
  ✗ Parallel processing CSV besar
  ✗ Excel (.xlsx) bukan CSV → trs/excelize atau github.com/360EntSecGroup-Skylar/excelize

Pertimbangkan encoding/json jika:
  ✗ Pertukaran data antar service — JSON lebih ekspresif untuk data hierarkis
  ✗ Data dengan tipe yang kompleks (nested, array)

Ringkasan #

  • reader.Read() mengembalikan io.EOF saat selesai — ini bukan error, tangani secara terpisah dengan if err == io.EOF { break }.
  • csv.Writer menggunakan buffer internal — selalu panggil writer.Flush() setelah selesai, dan periksa writer.Error() untuk mengetahui jika ada error saat buffering.
  • encoding/csv menangani edge case secara otomatis — field dengan koma, tanda kutip, dan newline dikutip dan di-escape dengan benar sesuai RFC 4180.
  • reader.TrimLeadingSpace = true untuk CSV yang punya spasi setelah delimiter — umum di file CSV yang dibuat manusia atau diekspor dari spreadsheet.
  • reader.LazyQuotes = true untuk toleransi CSV yang tidak sempurna — berguna untuk file dari sistem legacy yang tidak sepenuhnya mengikuti RFC 4180.
  • reader.FieldsPerRecord = -1 untuk CSV dengan jumlah field yang tidak konsisten — default (0) menggunakan baris pertama sebagai acuan dan error jika baris lain berbeda.
  • reader.ReuseRecord = true untuk performa maksimal — tapi simpan salinan dengan copy() jika ingin menyimpan record, karena slice asli akan di-overwrite di iterasi berikutnya.
  • Buat map header → indeks saat membaca CSV dengan header — lebih robust dari mengakses record[0], record[1] secara hardcoded jika urutan kolom berubah.
  • Commit database per batch saat import CSV besar — jangan satu transaksi untuk jutaan baris, commit setiap N baris untuk menghindari transaksi yang terlalu besar.

← Sebelumnya: Regexp   Berikutnya: Crypto →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact