MSSQL

MSSQL #

Microsoft SQL Server (MSSQL) adalah sistem manajemen basis data relasional enterprise dari Microsoft yang banyak digunakan di lingkungan korporat — terutama di ekosistem Windows/.NET, tapi juga di aplikasi berbasis JVM/Kotlin. MSSQL punya beberapa perbedaan penting dari MySQL: sintaks T-SQL yang berbeda, autentikasi Windows terintegrasi, fitur enterprise seperti Always On, dan dukungan stored procedure yang kuat. Di Kotlin, kamu berinteraksi dengan MSSQL melalui JDBC driver dari Microsoft, HikariCP untuk connection pooling, dan ORM seperti Exposed. Artikel ini membahas semua aspek dari koneksi dasar, CRUD, fitur khas T-SQL, hingga penanganan stored procedure dan perbedaan yang perlu diperhatikan saat migrasi dari MySQL.

Setup dan Dependensi #

// build.gradle.kts
dependencies {
    // Microsoft JDBC Driver untuk SQL Server
    implementation("com.microsoft.sqlserver:mssql-jdbc:12.6.1.jre11")

    // HikariCP connection pool
    implementation("com.zaxxer:HikariCP:5.1.0")

    // Exposed ORM (opsional)
    implementation("org.jetbrains.exposed:exposed-core:0.49.0")
    implementation("org.jetbrains.exposed:exposed-dao:0.49.0")
    implementation("org.jetbrains.exposed:exposed-jdbc:0.49.0")

    // Flyway untuk migrasi (SQL Server edition)
    implementation("org.flywaydb:flyway-core:10.10.0")
    implementation("org.flywaydb:flyway-sqlserver:10.10.0")
}

Format Connection String #

MSSQL memiliki format connection string yang berbeda dari MySQL:

// Format dasar dengan SQL Server Authentication
val urlDasar = "jdbc:sqlserver://localhost:1433;databaseName=myapp;encrypt=false"

// Format lengkap dengan semua opsi
val urlLengkap = buildString {
    append("jdbc:sqlserver://")
    append("localhost:1433")                    // host:port (default 1433)
    append(";databaseName=myapp")              // nama database
    append(";encrypt=true")                    // enkripsi koneksi
    append(";trustServerCertificate=true")     // percayai sertifikat self-signed (dev)
    append(";loginTimeout=15")                 // timeout login dalam detik
    append(";sendStringParametersAsUnicode=true") // UTF-8 support
    append(";applicationName=MyKotlinApp")     // nama aplikasi untuk monitoring
}

// Windows Authentication (tanpa username/password)
val urlWindowsAuth = "jdbc:sqlserver://localhost:1433;databaseName=myapp;integratedSecurity=true;encrypt=false"

// Azure SQL Database
val urlAzure = buildString {
    append("jdbc:sqlserver://myserver.database.windows.net:1433")
    append(";databaseName=myapp")
    append(";encrypt=true")
    append(";trustServerCertificate=false")
    append(";hostNameInCertificate=*.database.windows.net")
    append(";loginTimeout=30")
    append(";authentication=ActiveDirectoryPassword")  // atau SqlPassword
}

Koneksi dengan HikariCP #

import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource

object DatabaseMssql {
    private val dataSource: HikariDataSource by lazy {
        val config = HikariConfig().apply {
            jdbcUrl = buildString {
                append("jdbc:sqlserver://")
                append(System.getenv("DB_HOST") ?: "localhost:1433")
                append(";databaseName=${System.getenv("DB_NAME") ?: "myapp"}")
                append(";encrypt=${System.getenv("DB_ENCRYPT") ?: "false"}")
                append(";trustServerCertificate=true")
                append(";sendStringParametersAsUnicode=true")
            }
            driverClassName = "com.microsoft.sqlserver.jdbc.SQLServerDriver"
            username = System.getenv("DB_USER") ?: "sa"
            password = System.getenv("DB_PASSWORD") ?: "Password123!"

            // Pool settings
            minimumIdle = 2
            maximumPoolSize = 10
            idleTimeout = 300_000         // 5 menit
            connectionTimeout = 30_000    // 30 detik
            maxLifetime = 1_800_000       // 30 menit
            poolName = "MSSQL-Pool"

            // Test query untuk validasi koneksi
            connectionTestQuery = "SELECT 1"
        }
        HikariDataSource(config)
    }

    fun <T> gunakan(blok: (java.sql.Connection) -> T): T {
        return dataSource.connection.use(blok)
    }

    fun tutup() {
        if (!dataSource.isClosed) dataSource.close()
    }
}

fun main() {
    DatabaseMssql.gunakan { koneksi ->
        koneksi.createStatement().use { stmt ->
            stmt.executeQuery("SELECT @@VERSION").use { rs ->
                if (rs.next()) println("SQL Server: ${rs.getString(1).lines().first()}")
            }
        }
    }
}

Perbedaan T-SQL vs MySQL SQL #

MSSQL menggunakan dialek T-SQL yang berbeda dari MySQL. Ini adalah perbedaan yang perlu kamu perhatikan saat menulis query:

-- AUTO INCREMENT
-- MySQL:
CREATE TABLE pengguna (id INT AUTO_INCREMENT PRIMARY KEY, ...);

-- MSSQL (T-SQL):
CREATE TABLE pengguna (id INT IDENTITY(1,1) PRIMARY KEY, ...);

-- LIMIT / PAGING
-- MySQL:
SELECT * FROM produk ORDER BY nama LIMIT 10 OFFSET 20;

-- MSSQL:
SELECT * FROM produk ORDER BY nama OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;

-- String functions
-- MySQL: IFNULL(), GROUP_CONCAT()
-- MSSQL: ISNULL(), STRING_AGG()

-- Date functions
-- MySQL: NOW(), DATE_FORMAT()
-- MSSQL: GETDATE(), FORMAT()

-- Boolean
-- MySQL: BOOLEAN (alias untuk TINYINT(1)), nilai TRUE/FALSE
-- MSSQL: BIT (0 atau 1, tidak ada TRUE/FALSE literal)

-- Backtick vs Square bracket
-- MySQL: SELECT `nama` FROM `produk`
-- MSSQL: SELECT [nama] FROM [produk]  atau  SELECT "nama" FROM "produk"

Schema Tabel MSSQL #

CREATE TABLE dbo.produk (
    id              INT IDENTITY(1,1) PRIMARY KEY,
    nama            NVARCHAR(255)   NOT NULL,        -- N prefix untuk Unicode
    deskripsi       NVARCHAR(MAX),                   -- setara TEXT di MySQL
    harga           DECIMAL(15,2)   NOT NULL,
    stok            INT             NOT NULL DEFAULT 0,
    kategori        NVARCHAR(100),
    aktif           BIT             NOT NULL DEFAULT 1,  -- BIT bukan BOOLEAN
    dibuat_pada     DATETIME2       DEFAULT GETDATE(),
    diperbarui_pada DATETIME2       DEFAULT GETDATE()
);

-- Index
CREATE INDEX IX_produk_kategori ON dbo.produk(kategori);
CREATE INDEX IX_produk_aktif ON dbo.produk(aktif);
Gunakan NVARCHAR (bukan VARCHAR) untuk kolom teks yang menyimpan karakter non-ASCII seperti huruf Indonesia, Arab, atau karakter Unicode lainnya. NVARCHAR menyimpan dalam format UTF-16, sehingga panjang karakter dihitung berbeda — NVARCHAR(100) menyimpan 100 karakter Unicode.

CRUD dengan JDBC #

Model dan Repository #

import java.math.BigDecimal
import java.sql.ResultSet
import java.sql.Types

data class Produk(
    val id: Int = 0,
    val nama: String,
    val deskripsi: String? = null,
    val harga: BigDecimal,
    val stok: Int = 0,
    val kategori: String? = null,
    val aktif: Boolean = true
)

class ProdukRepositoryMssql {

    private fun ResultSet.toProduk() = Produk(
        id        = getInt("id"),
        nama      = getString("nama"),
        deskripsi = getString("deskripsi"),
        harga     = getBigDecimal("harga"),
        stok      = getInt("stok"),
        kategori  = getString("kategori"),
        aktif     = getBoolean("aktif")  // BIT otomatis dikonversi ke Boolean
    )

    // INSERT — MSSQL menggunakan OUTPUT INSERTED.id untuk mendapatkan ID yang di-generate
    fun simpan(produk: Produk): Produk {
        val sql = """
            INSERT INTO dbo.produk (nama, deskripsi, harga, stok, kategori, aktif)
            OUTPUT INSERTED.id
            VALUES (?, ?, ?, ?, ?, ?)
        """.trimIndent()

        return DatabaseMssql.gunakan { koneksi ->
            koneksi.prepareStatement(sql).use { stmt ->
                stmt.setString(1, produk.nama)
                // Null handling — setNull untuk kolom nullable
                if (produk.deskripsi != null) stmt.setString(2, produk.deskripsi)
                else stmt.setNull(2, Types.NVARCHAR)

                stmt.setBigDecimal(3, produk.harga)
                stmt.setInt(4, produk.stok)

                if (produk.kategori != null) stmt.setString(5, produk.kategori)
                else stmt.setNull(5, Types.NVARCHAR)

                stmt.setBoolean(6, produk.aktif)

                stmt.executeQuery().use { rs ->
                    if (rs.next()) produk.copy(id = rs.getInt(1))
                    else throw RuntimeException("Gagal mendapatkan ID yang di-generate")
                }
            }
        }
    }

    // SELECT dengan paginasi MSSQL (OFFSET...FETCH)
    fun cariSemua(
        kategori: String? = null,
        aktifSaja: Boolean = true,
        halaman: Int = 1,
        ukuran: Int = 20
    ): List<Produk> {
        val offset = (halaman - 1) * ukuran
        val kondisi = mutableListOf<String>()
        val params = mutableListOf<Any?>()

        if (aktifSaja) kondisi.add("aktif = 1")
        if (kategori != null) {
            kondisi.add("kategori = ?")
            params.add(kategori)
        }

        val whereClause = if (kondisi.isNotEmpty()) "WHERE ${kondisi.joinToString(" AND ")}" else ""

        val sql = """
            SELECT *
            FROM dbo.produk
            $whereClause
            ORDER BY nama
            OFFSET ? ROWS
            FETCH NEXT ? ROWS ONLY
        """.trimIndent()

        return DatabaseMssql.gunakan { koneksi ->
            koneksi.prepareStatement(sql).use { stmt ->
                var idx = 1
                params.forEach { p ->
                    when (p) {
                        is String -> stmt.setString(idx++, p)
                        else -> stmt.setObject(idx++, p)
                    }
                }
                stmt.setInt(idx++, offset)
                stmt.setInt(idx, ukuran)

                stmt.executeQuery().use { rs ->
                    buildList { while (rs.next()) add(rs.toProduk()) }
                }
            }
        }
    }

    // SELECT dengan MSSQL TOP (alternatif paginasi)
    fun ambilTop(n: Int, aktifSaja: Boolean = true): List<Produk> {
        val sql = """
            SELECT TOP (?) *
            FROM dbo.produk
            ${if (aktifSaja) "WHERE aktif = 1" else ""}
            ORDER BY harga DESC
        """.trimIndent()

        return DatabaseMssql.gunakan { koneksi ->
            koneksi.prepareStatement(sql).use { stmt ->
                stmt.setInt(1, n)
                stmt.executeQuery().use { rs ->
                    buildList { while (rs.next()) add(rs.toProduk()) }
                }
            }
        }
    }

    // UPDATE
    fun perbarui(produk: Produk): Boolean {
        val sql = """
            UPDATE dbo.produk
            SET nama = ?, deskripsi = ?, harga = ?, stok = ?, kategori = ?
            WHERE id = ?
        """.trimIndent()

        return DatabaseMssql.gunakan { koneksi ->
            koneksi.prepareStatement(sql).use { stmt ->
                stmt.setString(1, produk.nama)
                if (produk.deskripsi != null) stmt.setString(2, produk.deskripsi)
                else stmt.setNull(2, Types.NVARCHAR)
                stmt.setBigDecimal(3, produk.harga)
                stmt.setInt(4, produk.stok)
                if (produk.kategori != null) stmt.setString(5, produk.kategori)
                else stmt.setNull(5, Types.NVARCHAR)
                stmt.setInt(6, produk.id)
                stmt.executeUpdate() > 0
            }
        }
    }

    // MERGE (UPSERT) — fitur khas T-SQL
    fun upsert(produk: Produk): Produk {
        val sql = """
            MERGE dbo.produk AS target
            USING (SELECT ? AS nama, ? AS harga, ? AS stok, ? AS kategori) AS source
                ON target.nama = source.nama
            WHEN MATCHED THEN
                UPDATE SET harga = source.harga, stok = source.stok
            WHEN NOT MATCHED THEN
                INSERT (nama, harga, stok, kategori)
                VALUES (source.nama, source.harga, source.stok, source.kategori)
            OUTPUT INSERTED.id;
        """.trimIndent()

        return DatabaseMssql.gunakan { koneksi ->
            koneksi.prepareStatement(sql).use { stmt ->
                stmt.setString(1, produk.nama)
                stmt.setBigDecimal(2, produk.harga)
                stmt.setInt(3, produk.stok)
                if (produk.kategori != null) stmt.setString(4, produk.kategori)
                else stmt.setNull(4, Types.NVARCHAR)

                stmt.executeQuery().use { rs ->
                    if (rs.next()) produk.copy(id = rs.getInt(1))
                    else produk
                }
            }
        }
    }
}

Transaction di MSSQL #

fun transferStokMssql(dariId: Int, keId: Int, jumlah: Int) {
    DatabaseMssql.gunakan { koneksi ->
        koneksi.autoCommit = false

        try {
            // Gunakan WITH (UPDLOCK, ROWLOCK) untuk mencegah deadlock
            val sqlCekStok = """
                SELECT stok FROM dbo.produk WITH (UPDLOCK, ROWLOCK)
                WHERE id = ?
            """.trimIndent()

            val stokSumber = koneksi.prepareStatement(sqlCekStok).use { stmt ->
                stmt.setInt(1, dariId)
                stmt.executeQuery().use { rs ->
                    if (!rs.next()) throw NoSuchElementException("Produk $dariId tidak ditemukan")
                    rs.getInt("stok")
                }
            }

            if (stokSumber < jumlah) {
                throw IllegalStateException("Stok tidak cukup: tersedia $stokSumber, diminta $jumlah")
            }

            koneksi.prepareStatement("UPDATE dbo.produk SET stok = stok - ? WHERE id = ?").use { stmt ->
                stmt.setInt(1, jumlah)
                stmt.setInt(2, dariId)
                stmt.executeUpdate()
            }

            koneksi.prepareStatement("UPDATE dbo.produk SET stok = stok + ? WHERE id = ?").use { stmt ->
                stmt.setInt(1, jumlah)
                stmt.setInt(2, keId)
                stmt.executeUpdate()
            }

            koneksi.commit()
            println("Transfer berhasil: $jumlah unit dari produk $dariId ke $keId")

        } catch (e: Exception) {
            koneksi.rollback()
            println("Transfer gagal, rollback: ${e.message}")
            throw e
        } finally {
            koneksi.autoCommit = true
        }
    }
}

Stored Procedure #

Stored procedure adalah fitur kuat MSSQL yang sering digunakan di lingkungan enterprise. Kotlin bisa memanggil stored procedure melalui CallableStatement:

-- Definisi stored procedure di SQL Server
CREATE PROCEDURE dbo.sp_CariProduk
    @Kategori NVARCHAR(100) = NULL,
    @HargaMaks DECIMAL(15,2) = NULL,
    @Limit INT = 20
AS
BEGIN
    SET NOCOUNT ON;

    SELECT TOP (@Limit) id, nama, harga, stok, kategori
    FROM dbo.produk
    WHERE aktif = 1
      AND (@Kategori IS NULL OR kategori = @Kategori)
      AND (@HargaMaks IS NULL OR harga <= @HargaMaks)
    ORDER BY nama;
END;
GO

-- Stored procedure dengan output parameter
CREATE PROCEDURE dbo.sp_HitungStokTotal
    @Kategori NVARCHAR(100),
    @TotalStok INT OUTPUT,
    @JumlahProduk INT OUTPUT
AS
BEGIN
    SELECT
        @TotalStok = SUM(stok),
        @JumlahProduk = COUNT(*)
    FROM dbo.produk
    WHERE aktif = 1 AND kategori = @Kategori;
END;
GO
// Memanggil stored procedure
fun cariProdukViaStoredProcedure(
    kategori: String? = null,
    hargaMaks: BigDecimal? = null,
    limit: Int = 20
): List<Produk> {
    return DatabaseMssql.gunakan { koneksi ->
        // {call NamaStoredProcedure(?, ?, ?)}
        koneksi.prepareCall("{call dbo.sp_CariProduk(?, ?, ?)}").use { stmt ->
            // Named parameter atau positional
            if (kategori != null) stmt.setString(1, kategori)
            else stmt.setNull(1, Types.NVARCHAR)

            if (hargaMaks != null) stmt.setBigDecimal(2, hargaMaks)
            else stmt.setNull(2, Types.DECIMAL)

            stmt.setInt(3, limit)

            stmt.executeQuery().use { rs ->
                buildList {
                    while (rs.next()) {
                        add(Produk(
                            id       = rs.getInt("id"),
                            nama     = rs.getString("nama"),
                            harga    = rs.getBigDecimal("harga"),
                            stok     = rs.getInt("stok"),
                            kategori = rs.getString("kategori")
                        ))
                    }
                }
            }
        }
    }
}

// Stored procedure dengan output parameter
fun hitungStokKategori(kategori: String): Pair<Int, Int> {
    return DatabaseMssql.gunakan { koneksi ->
        koneksi.prepareCall("{call dbo.sp_HitungStokTotal(?, ?, ?)}").use { stmt ->
            stmt.setString(1, kategori)

            // Daftarkan output parameter
            stmt.registerOutParameter(2, Types.INTEGER)  // @TotalStok
            stmt.registerOutParameter(3, Types.INTEGER)  // @JumlahProduk

            stmt.execute()

            val totalStok = stmt.getInt(2)
            val jumlahProduk = stmt.getInt(3)

            Pair(totalStok, jumlahProduk)
        }
    }
}

// Penggunaan
fun main() {
    val produk = cariProdukViaStoredProcedure(kategori = "Elektronik", limit = 5)
    produk.forEach { println("${it.nama}: Rp${it.harga}") }

    val (totalStok, jumlahProduk) = hitungStokKategori("Elektronik")
    println("Elektronik: $jumlahProduk produk, total stok $totalStok unit")
}

Exposed dengan MSSQL #

Exposed bisa digunakan dengan MSSQL dengan penyesuaian dialect:

import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction

object TabelProdukMssql : Table("dbo.produk") {
    val id        = integer("id").autoIncrement()
    val nama      = varchar("nama", 255)
    val deskripsi = text("deskripsi").nullable()
    val harga     = decimal("harga", 15, 2)
    val stok      = integer("stok").default(0)
    val kategori  = varchar("kategori", 100).nullable()
    val aktif     = bool("aktif").default(true)

    override val primaryKey = PrimaryKey(id)
}

fun inisialisasiExposedMssql() {
    // Gunakan DataSource dari HikariCP
    Database.connect(DatabaseMssql.dataSource)
}

// Query dengan Exposed — sama seperti MySQL
fun cariProdukExposed(kategori: String? = null) = transaction {
    TabelProdukMssql
        .select { TabelProdukMssql.aktif eq true }
        .apply {
            if (kategori != null) {
                andWhere { TabelProdukMssql.kategori eq kategori }
            }
        }
        .orderBy(TabelProdukMssql.nama)
        .map { baris ->
            Produk(
                id       = baris[TabelProdukMssql.id],
                nama     = baris[TabelProdukMssql.nama],
                harga    = baris[TabelProdukMssql.harga],
                stok     = baris[TabelProdukMssql.stok],
                kategori = baris[TabelProdukMssql.kategori],
                aktif    = baris[TabelProdukMssql.aktif]
            )
        }
}

Migrasi dengan Flyway untuk MSSQL #

import org.flywaydb.core.Flyway

fun jalankanMigrasiMssql() {
    val flyway = Flyway.configure()
        .dataSource(DatabaseMssql.dataSource)
        .locations("classpath:db/migration/mssql")
        .defaultSchema("dbo")
        .baselineOnMigrate(true)
        .validateOnMigrate(true)
        .load()

    val hasil = flyway.migrate()
    println("MSSQL Migration: ${hasil.migrationsExecuted} migrasi dijalankan")
}

File migrasi untuk MSSQL harus menggunakan sintaks T-SQL:

-- src/main/resources/db/migration/mssql/V1__create_produk.sql
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'produk' AND schema_id = SCHEMA_ID('dbo'))
BEGIN
    CREATE TABLE dbo.produk (
        id              INT IDENTITY(1,1) PRIMARY KEY,
        nama            NVARCHAR(255)   NOT NULL,
        deskripsi       NVARCHAR(MAX),
        harga           DECIMAL(15,2)   NOT NULL,
        stok            INT             NOT NULL DEFAULT 0,
        kategori        NVARCHAR(100),
        aktif           BIT             NOT NULL DEFAULT 1,
        dibuat_pada     DATETIME2       DEFAULT GETDATE()
    );

    CREATE INDEX IX_produk_kategori ON dbo.produk(kategori);
    CREATE INDEX IX_produk_aktif ON dbo.produk(aktif);
END;
GO

Tips Khusus MSSQL #

Windows Authentication #

// Jika aplikasi berjalan di Windows dan perlu Windows Authentication
val urlWindowsAuth = buildString {
    append("jdbc:sqlserver://localhost:1433")
    append(";databaseName=myapp")
    append(";integratedSecurity=true")
    append(";encrypt=false")
    // Tidak perlu username/password — menggunakan kredensial Windows pengguna saat ini
}

// Untuk Windows Auth, tambahkan DLL ke PATH atau dependency
// implementation("com.microsoft.sqlserver:mssql-jdbc_auth:12.6.1.x64") // Windows only

Menangani Deadlock #

MSSQL lebih agresif dalam mendeteksi deadlock dibanding MySQL. Tangani SQLServerException dengan kode error 1205:

import com.microsoft.sqlserver.jdbc.SQLServerException

fun eksekusiDenganRetry(blok: () -> Unit, maksPercobaan: Int = 3) {
    var percobaan = 0
    while (percobaan < maksPercobaan) {
        try {
            blok()
            return
        } catch (e: SQLServerException) {
            if (e.errorCode == 1205 && percobaan < maksPercobaan - 1) {
                // Error 1205 = deadlock victim
                percobaan++
                println("Deadlock terdeteksi, percobaan ke-$percobaan...")
                Thread.sleep(100L * percobaan)  // exponential backoff sederhana
            } else {
                throw e
            }
        }
    }
}

Ringkasan #

  • Driver Microsoft resmi — gunakan com.microsoft.sqlserver:mssql-jdbc (bukan jtds yang sudah ketinggalan zaman). Driver Microsoft mendukung semua fitur SQL Server modern termasuk enkripsi dan AAD.
  • NVARCHAR untuk teks Unicode — selalu gunakan NVARCHAR (bukan VARCHAR) untuk kolom yang menyimpan teks yang mungkin mengandung karakter non-ASCII. Di JDBC, setNString() atau setString() keduanya bekerja dengan NVARCHAR.
  • OUTPUT INSERTED untuk mendapat ID — di MSSQL, INSERT ... OUTPUT INSERTED.id lebih andal dari getGeneratedKeys() untuk mendapatkan nilai IDENTITY yang baru di-generate. Ini juga bisa mengembalikan banyak kolom sekaligus.
  • OFFSET…FETCH untuk paginasi — gantikan LIMIT...OFFSET MySQL dengan OFFSET n ROWS FETCH NEXT m ROWS ONLY di T-SQL. Klausa ORDER BY wajib ada saat menggunakan OFFSET.
  • MERGE untuk upsert — T-SQL punya sintaks MERGE yang powerful untuk operasi insert-or-update berdasarkan kondisi. Lebih eksplisit dan fleksibel dari INSERT ... ON DUPLICATE KEY UPDATE di MySQL.
  • Stored procedure dengan CallableStatement — di lingkungan enterprise MSSQL, stored procedure sering digunakan untuk logika bisnis di sisi database. CallableStatement dengan sintaks {call sp_nama(?, ?)} menangani parameter input dan output.
  • WITH (UPDLOCK, ROWLOCK) untuk mencegah deadlock — saat membaca data yang akan segera diupdate dalam satu transaction, gunakan hint ini untuk mendapatkan kunci lebih awal dan menghindari deadlock.
  • Tangani deadlock dengan retry — MSSQL memilih salah satu transaksi sebagai “deadlock victim” (error 1205) dan rollback. Tangani ini dengan mekanisme retry dengan exponential backoff.

← Sebelumnya: MySQL   Berikutnya: Oracle →

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