Database
 sql >> Teknologi Basis Data >  >> RDS >> Database

Membuat Kasus untuk BUKAN Pemicu – Bagian 1

Tahun lalu, saya memposting tip yang disebut Tingkatkan Efisiensi SQL Server dengan Beralih ke BUKAN Pemicu.

Alasan utama saya cenderung menyukai pemicu BUKAN, terutama dalam kasus di mana saya mengharapkan banyak pelanggaran logika bisnis, adalah bahwa tampaknya intuitif bahwa akan lebih murah untuk mencegah suatu tindakan sama sekali, daripada melanjutkan dan melakukannya (dan log it!), hanya untuk menggunakan pemicu SETELAH untuk menghapus baris yang menyinggung (atau memutar kembali seluruh operasi). Hasil yang ditunjukkan pada tip tersebut menunjukkan bahwa ini adalah kasusnya – dan saya menduga mereka akan lebih jelas dengan lebih banyak indeks non-cluster yang terpengaruh oleh operasi.

Namun, itu pada disk yang lambat, dan pada CTP awal SQL Server 2014. Dalam mempersiapkan slide untuk presentasi baru yang akan saya lakukan tahun ini pada pemicu, saya menemukan bahwa pada build SQL Server 2014 yang lebih baru – dikombinasikan dengan perangkat keras yang diperbarui – agak sulit untuk mendemonstrasikan delta yang sama dalam kinerja antara pemicu SETELAH dan BUKAN. Jadi saya mulai mencari tahu alasannya, meskipun saya langsung tahu ini akan menjadi lebih banyak pekerjaan daripada yang pernah saya lakukan untuk satu slide.

Satu hal yang ingin saya sebutkan adalah bahwa pemicu dapat menggunakan tempdb dengan cara yang berbeda, dan ini mungkin menjelaskan beberapa perbedaan ini. Pemicu SETELAH menggunakan penyimpanan versi untuk tabel semu yang dimasukkan dan dihapus, sementara pemicu BUKAN membuat salinan data ini di meja kerja internal. Perbedaannya tidak kentara, tetapi patut ditunjukkan.

Variabel

Saya akan menguji berbagai skenario, termasuk:

  • Tiga pemicu berbeda:
    • Pemicu AFTER yang menghapus baris tertentu yang gagal
    • Pemicu AFTER yang mengembalikan seluruh transaksi jika ada baris yang gagal
    • BUKAN pemicu yang hanya menyisipkan baris yang lewat
  • Model pemulihan dan setelan isolasi snapshot yang berbeda:
    • PENUH dengan SNAPSHOT diaktifkan
    • PENUH dengan SNAPSHOT dinonaktifkan
    • SEDERHANA dengan SNAPSHOT diaktifkan
    • SEDERHANA dengan SNAPSHOT dinonaktifkan
  • Tata letak disk yang berbeda*:
    • Data di SSD, masuk ke HDD 7200 RPM
    • Data di SSD, masuk ke SSD
    • Data di HDD 7200 RPM, masuk ke SSD
    • Data di HDD 7200 RPM, masuk ke HDD 7200 RPM
  • Tingkat kegagalan yang berbeda:
    • tingkat kegagalan 10%, 25%, dan 50% di seluruh:
      • Insersi batch tunggal dengan 20.000 baris
      • 10 kumpulan 2.000 baris
      • 100 kumpulan 200 baris
      • 1.000 kumpulan 20 baris
      • 20.000 sisipan tunggal

    * tempdb adalah file data tunggal pada disk 7200 RPM yang lambat. Ini disengaja dan dimaksudkan untuk memperkuat kemacetan yang disebabkan oleh berbagai penggunaan tempdb . Saya berencana untuk mengunjungi kembali tes ini di beberapa titik ketika tempdb menggunakan SSD yang lebih cepat.

Oke, TL;DR Sudah!

Jika Anda hanya ingin tahu hasilnya, lewati saja. Segala sesuatu di tengah hanyalah latar belakang dan penjelasan tentang bagaimana saya mengatur dan menjalankan tes. Saya tidak patah hati karena tidak semua orang akan tertarik pada semua hal kecil.

Skenario

Untuk rangkaian pengujian khusus ini, skenario kehidupan nyata adalah skenario di mana pengguna memilih nama layar, dan pemicu dirancang untuk menangkap kasus di mana nama yang dipilih melanggar beberapa aturan. Misalnya, tidak boleh ada variasi "ninny-muggins" (Anda tentu bisa menggunakan imajinasi Anda di sini).

Saya membuat tabel dengan 20.000 nama pengguna unik:

USE model;
GO
 
-- 20,000 distinct, good Names
;WITH distinct_Names AS
(
  SELECT Name FROM sys.all_columns
  UNION 
  SELECT Name FROM sys.all_objects
)
SELECT TOP (20000) Name 
INTO dbo.GoodNamesSource
FROM
(
  SELECT Name FROM distinct_Names
  UNION 
  SELECT Name + 'x' FROM distinct_Names
  UNION 
  SELECT Name + 'y' FROM distinct_Names
  UNION 
  SELECT Name + 'z' FROM distinct_Names
) AS x;
 
CREATE UNIQUE CLUSTERED INDEX x ON dbo.GoodNamesSource(Name);

Kemudian saya membuat tabel yang akan menjadi sumber untuk "nama nakal" saya untuk diperiksa. Dalam hal ini hanya ninny-muggins-00001 melalui ninny-muggins-10000 :

USE model;
GO
 
CREATE TABLE dbo.NaughtyUserNames
(
  Name NVARCHAR(255) PRIMARY KEY
);
GO
 
-- 10,000 "bad" names
INSERT dbo.NaughtyUserNames(Name)
  SELECT N'ninny-muggins-' + RIGHT(N'0000' + RTRIM(n),5)
  FROM
  (
    SELECT TOP (10000) n = ROW_NUMBER() OVER (ORDER BY Name)
	FROM dbo.GoodNamesSource
  ) AS x;

Saya membuat tabel ini dalam model database sehingga setiap kali saya membuat database, itu akan ada secara lokal, dan saya berencana untuk membuat banyak database untuk menguji matriks skenario yang tercantum di atas (daripada hanya mengubah pengaturan database, menghapus log, dll). Harap diperhatikan, jika Anda membuat objek dalam model untuk tujuan pengujian, pastikan Anda menghapus objek tersebut setelah selesai.

Selain itu, saya akan dengan sengaja mengabaikan pelanggaran utama dan penanganan kesalahan lainnya dari ini, membuat asumsi naif bahwa nama yang dipilih diperiksa keunikannya jauh sebelum penyisipan dicoba, tetapi dalam transaksi yang sama (seperti cek terhadap tabel nama nakal bisa saja dibuat terlebih dahulu).

Untuk mendukung ini, saya juga membuat tiga tabel berikut yang hampir identik di model , untuk tujuan isolasi pengujian:

USE model;
GO
 
 
-- AFTER (rollback)
CREATE TABLE dbo.UserNames_After_Rollback
(
  UserID INT IDENTITY(1,1) PRIMARY KEY,
  Name NVARCHAR(255) NOT NULL UNIQUE,
  DateCreated DATE NOT NULL DEFAULT SYSDATETIME()
);
CREATE INDEX x ON dbo.UserNames_After_Rollback(DateCreated) INCLUDE(Name);
 
 
-- AFTER (delete)
CREATE TABLE dbo.UserNames_After_Delete
(
  UserID INT IDENTITY(1,1) PRIMARY KEY,
  Name NVARCHAR(255) NOT NULL UNIQUE,
  DateCreated DATE NOT NULL DEFAULT SYSDATETIME()
);
CREATE INDEX x ON dbo.UserNames_After_Delete(DateCreated) INCLUDE(Name);
 
 
-- INSTEAD
CREATE TABLE dbo.UserNames_Instead
(
  UserID INT IDENTITY(1,1) PRIMARY KEY,
  Name NVARCHAR(255) NOT NULL UNIQUE,
  DateCreated DATE NOT NULL DEFAULT SYSDATETIME()
);
CREATE INDEX x ON dbo.UserNames_Instead(DateCreated) INCLUDE(Name);
GO

Dan tiga pemicu berikut, satu untuk setiap tabel:

USE model;
GO
 
 
-- AFTER (rollback)
CREATE TRIGGER dbo.trUserNames_After_Rollback
ON dbo.UserNames_After_Rollback
AFTER INSERT
AS
BEGIN
  SET NOCOUNT ON;
 
  IF EXISTS 
  (
   SELECT 1 FROM inserted AS i
    WHERE EXISTS
    (
      SELECT 1 FROM dbo.NaughtyUserNames
      WHERE Name = i.Name
    )
  )
  BEGIN
    ROLLBACK TRANSACTION;
  END
END
GO
 
 
-- AFTER (delete)
CREATE TRIGGER dbo.trUserNames_After_Delete
ON dbo.UserNames_After_Delete
AFTER INSERT
AS
BEGIN
  SET NOCOUNT ON;
 
  DELETE d
    FROM inserted AS i
    INNER JOIN dbo.NaughtyUserNames AS n
    ON i.Name = n.Name
    INNER JOIN dbo.UserNames_After_Delete AS d
    ON i.UserID = d.UserID;
END
GO
 
 
-- INSTEAD
CREATE TRIGGER dbo.trUserNames_Instead
ON dbo.UserNames_Instead
INSTEAD OF INSERT
AS
BEGIN
  SET NOCOUNT ON;
 
  INSERT dbo.UserNames_Instead(Name)
    SELECT i.Name
      FROM inserted AS i
      WHERE NOT EXISTS
      (
        SELECT 1 FROM dbo.NaughtyUserNames
        WHERE Name = i.Name
      );
END
GO

Anda mungkin ingin mempertimbangkan penanganan tambahan untuk memberi tahu pengguna bahwa pilihan mereka dibatalkan atau diabaikan – tetapi ini juga diabaikan untuk kesederhanaan.

Pengaturan Pengujian

Saya membuat data sampel yang mewakili tiga tingkat kegagalan yang ingin saya uji, mengubah 10 persen menjadi 25 dan kemudian 50, dan menambahkan tabel ini juga ke model :

USE model;
GO
 
DECLARE @pct INT = 10, @cap INT = 20000;
-- change this ----^^ to 25 and 50
 
DECLARE @good INT = @cap - (@cap*(@pct/100.0));
 
SELECT Name, rn = ROW_NUMBER() OVER (ORDER BY NEWID()) 
INTO dbo.Source10Percent FROM 
-- change this ^^ to 25 and 50
(
  SELECT Name FROM 
  (
    SELECT TOP (@good) Name FROM dbo.GoodNamesSource ORDER BY NEWID()
  ) AS g
  UNION ALL
  SELECT Name FROM 
  (
    SELECT TOP (@cap-@good) Name FROM dbo.NaughtyUserNames ORDER BY NEWID()
  ) AS b
) AS x;
 
CREATE UNIQUE CLUSTERED INDEX x ON dbo.Source10Percent(rn);
-- and here as well -------------------------^^

Setiap tabel memiliki 20.000 baris, dengan campuran nama yang berbeda yang akan lulus dan gagal, dan kolom nomor baris memudahkan untuk membagi data menjadi ukuran batch yang berbeda untuk pengujian yang berbeda, tetapi dengan tingkat kegagalan berulang untuk semua pengujian.

Tentu kita membutuhkan tempat untuk mengabadikan hasil tersebut. Saya memilih untuk menggunakan database terpisah untuk ini, menjalankan setiap pengujian beberapa kali, hanya merekam durasi.

CREATE DATABASE ControlDB;
GO
 
USE ControlDB;
GO
 
CREATE TABLE dbo.Tests
(
  TestID        INT, 
  DiskLayout    VARCHAR(15),
  RecoveryModel VARCHAR(6),
  TriggerType   VARCHAR(14),
  [snapshot]    VARCHAR(3),
  FailureRate   INT,
  [sql]         NVARCHAR(MAX)
);
 
CREATE TABLE dbo.TestResults
(
  TestID INT,
  BatchDescription VARCHAR(15),
  Duration INT
);

Saya mengisi dbo.Tests tabel dengan skrip berikut, sehingga saya dapat menjalankan bagian yang berbeda untuk menyiapkan empat database agar sesuai dengan parameter pengujian saat ini. Perhatikan bahwa D:\ adalah SSD, sedangkan G:\ adalah disk 7200 RPM:

TRUNCATE TABLE dbo.Tests;
TRUNCATE TABLE dbo.TestResults;
 
;WITH d AS 
(
  SELECT DiskLayout FROM (VALUES
    ('DataSSD_LogHDD'),
    ('DataSSD_LogSSD'),
    ('DataHDD_LogHDD'),
    ('DataHDD_LogSSD')) AS d(DiskLayout)
),
t AS 
(
  SELECT TriggerType FROM (VALUES
  ('After_Delete'),
  ('After_Rollback'),
  ('Instead')) AS t(TriggerType)
),
m AS 
(
  SELECT RecoveryModel = 'FULL' 
      UNION ALL SELECT 'SIMPLE'
),
s AS 
(
  SELECT IsSnapshot = 0 
      UNION ALL SELECT 1
),
p AS 
(
  SELECT FailureRate = 10 
      UNION ALL SELECT 25 
	  UNION ALL SELECT 50
)
INSERT ControlDB.dbo.Tests
(
  TestID, 
  DiskLayout, 
  RecoveryModel, 
  TriggerType, 
  IsSnapshot, 
  FailureRate, 
  Command
)
SELECT 
  TestID = ROW_NUMBER() OVER 
  (
    ORDER BY d.DiskLayout, t.TriggerType, m.RecoveryModel, s.IsSnapshot, p.FailureRate
  ),
  d.DiskLayout, 
  m.RecoveryModel, 
  t.TriggerType, 
  s.IsSnapshot, 
  p.FailureRate, 
  [sql]= N'SET NOCOUNT ON;
 
CREATE DATABASE ' + QUOTENAME(d.DiskLayout) 
 + N' ON (name = N''data'', filename = N''' + CASE d.DiskLayout 
WHEN 'DataSSD_LogHDD' THEN N'D:\data\data1.mdf'') 
  LOG ON (name = N''log'', filename = N''G:\log\data1.ldf'');'
WHEN 'DataSSD_LogSSD' THEN N'D:\data\data2.mdf'') 
  LOG ON (name = N''log'', filename = N''D:\log\data2.ldf'');'
WHEN 'DataHDD_LogHDD' THEN N'G:\data\data3.mdf'') 
  LOG ON (name = N''log'', filename = N''G:\log\data3.ldf'');'
WHEN 'DataHDD_LogSSD' THEN N'G:\data\data4.mdf'') 
  LOG ON (name = N''log'', filename = N''D:\log\data4.ldf'');' END
+ '
EXEC sp_executesql N''ALTER DATABASE ' + QUOTENAME(d.DiskLayout) 
  + ' SET RECOVERY ' + m.RecoveryModel + ';'';'
+ CASE WHEN s.IsSnapshot = 1 THEN 
'
EXEC sp_executesql N''ALTER DATABASE ' + QUOTENAME(d.DiskLayout) 
  + ' SET ALLOW_SNAPSHOT_ISOLATION ON;'';
EXEC sp_executesql N''ALTER DATABASE ' + QUOTENAME(d.DiskLayout) 
  + ' SET READ_COMMITTED_SNAPSHOT ON;'';' 
ELSE '' END
+ '
 
DECLARE @d DATETIME2(7), @i INT, @LoopID INT, @loops INT, @perloop INT;
 
DECLARE c CURSOR LOCAL FAST_FORWARD FOR
  SELECT LoopID, loops, perloop FROM dbo.Loops; 
 
OPEN c;
 
FETCH c INTO @LoopID, @loops, @perloop;
 
WHILE @@FETCH_STATUS <> -1
BEGIN
  EXEC sp_executesql N''TRUNCATE TABLE ' 
    + QUOTENAME(d.DiskLayout) + '.dbo.UserNames_' + t.TriggerType + ';'';
 
  SELECT @d = SYSDATETIME(), @i = 1;
 
  WHILE @i <= @loops
  BEGIN
    BEGIN TRY
      INSERT ' + QUOTENAME(d.DiskLayout) + '.dbo.UserNames_' + t.TriggerType + '(Name)
        SELECT Name FROM ' + QUOTENAME(d.DiskLayout) + '.dbo.Source' + RTRIM(p.FailureRate) + 'Percent
	    WHERE rn > (@i-1)*@perloop AND rn <= @i*@perloop;
    END TRY
    BEGIN CATCH
      SET @TestID = @TestID;
    END CATCH
 
    SET @i += 1;
  END
 
  INSERT ControlDB.dbo.TestResults(TestID, LoopID, Duration)
    SELECT @TestID, @LoopID, DATEDIFF(MILLISECOND, @d, SYSDATETIME());
 
  FETCH c INTO @LoopID, @loops, @perloop;
END
 
CLOSE c;
DEALLOCATE c;
 
DROP DATABASE ' + QUOTENAME(d.DiskLayout) + ';'
FROM d, t, m, s, p;  -- implicit CROSS JOIN! Do as I say, not as I do! :-)

Maka mudah untuk menjalankan semua tes beberapa kali:

USE ControlDB;
GO
 
SET NOCOUNT ON;
 
DECLARE @TestID INT, @Command NVARCHAR(MAX), @msg VARCHAR(32);
 
DECLARE d CURSOR LOCAL FAST_FORWARD FOR 
  SELECT TestID, Command
    FROM ControlDB.dbo.Tests ORDER BY TestID;
 
OPEN d;
 
FETCH d INTO @TestID, @Command;
 
WHILE @@FETCH_STATUS <> -1
BEGIN
  SET @msg = 'Starting ' + RTRIM(@TestID);
  RAISERROR(@msg, 0, 1) WITH NOWAIT;
 
  EXEC sp_executesql @Command, N'@TestID INT', @TestID;
 
  SET @msg = 'Finished ' + RTRIM(@TestID);
  RAISERROR(@msg, 0, 1) WITH NOWAIT;
 
  FETCH d INTO @TestID, @Command;
END
 
CLOSE d;
DEALLOCATE d;
 
GO 10

Di sistem saya ini membutuhkan waktu hampir 6 jam, jadi bersiaplah untuk membiarkan ini berjalan tanpa gangguan. Juga, pastikan Anda tidak memiliki koneksi aktif atau jendela kueri yang terbuka terhadap model database, jika tidak, Anda mungkin mendapatkan kesalahan ini saat skrip mencoba membuat database:

Msg 1807, Level 16, Status 3
Tidak dapat memperoleh kunci eksklusif pada 'model' basis data. Coba lagi operasinya nanti.

Hasil

Ada banyak titik data untuk dilihat (dan semua kueri yang digunakan untuk memperoleh data dirujuk dalam Lampiran). Ingatlah bahwa setiap durasi rata-rata yang dilambangkan di sini adalah lebih dari 10 pengujian dan memasukkan total 100.000 baris ke dalam tabel tujuan.

Grafik 1 – Keseluruhan Agregat

Grafik pertama menunjukkan agregat keseluruhan (durasi rata-rata) untuk variabel yang berbeda secara terpisah (jadi *semua* pengujian menggunakan pemicu AFTER yang menghapus, *semua* pengujian menggunakan pemicu AFTER yang memutar kembali, dll).


Durasi rata-rata, dalam milidetik, untuk setiap variabel dalam isolasi

Beberapa hal langsung muncul pada kami:

  • BUKAN pemicu di sini dua kali lebih cepat dari kedua pemicu SETELAH.
  • Memiliki log transaksi di SSD membuat sedikit perbedaan. Lokasi file data apalagi.
  • Batch 20.000 insert tunggal 7-8x lebih lambat daripada distribusi batch lainnya.
  • Insersi batch tunggal dari 20.000 baris lebih lambat daripada distribusi non-tunggal mana pun.
  • Tingkat kegagalan, isolasi snapshot, dan model pemulihan hanya berdampak kecil pada performa.

Grafik 2 – 10 Terbaik Secara Keseluruhan

Grafik ini menunjukkan 10 hasil tercepat ketika setiap variabel dipertimbangkan. Ini semua BUKAN pemicu di mana persentase terbesar baris gagal (50%). Anehnya, yang tercepat (meskipun tidak banyak) memiliki data dan masuk ke HDD yang sama (bukan SSD). Ada campuran tata letak disk dan model pemulihan di sini, tetapi semua 10 memiliki isolasi snapshot yang diaktifkan, dan 7 hasil teratas semuanya melibatkan ukuran batch 10 x 2.000 baris.


10 durasi terbaik, dalam milidetik, dengan mempertimbangkan setiap variabel

Pemicu AFTER tercepat – varian ROLLBACK dengan tingkat kegagalan 10% dalam ukuran batch baris 100 x 200 – berada di posisi #144 (806 md).

Grafik 3 – 10 Terburuk Secara Keseluruhan

Grafik ini menunjukkan 10 hasil paling lambat ketika setiap variabel dipertimbangkan; semua varian SETELAH, semua melibatkan 20.000 sisipan tunggal, dan semua memiliki data dan log pada HDD lambat yang sama.


10 durasi terburuk, dalam milidetik, dengan mempertimbangkan setiap variabel

BUKAN pengujian paling lambat berada di posisi #97, pada 5.680 ms – pengujian insert tunggal 20.000 di mana 10% gagal. Menarik juga untuk mengamati bahwa tidak satu pun pemicu SETELAH menggunakan ukuran batch insert 20.000 singleton bernasib lebih baik – sebenarnya hasil terburuk ke-96 adalah tes SETELAH (hapus) yang datang pada 10.219 ms – hampir dua kali lipat hasil paling lambat berikutnya.

Grafik 4 – Jenis Disk Log, Sisipan Tunggal

Grafik di atas memberi kita gambaran kasar tentang masalah terbesar, tetapi grafik tersebut terlalu diperbesar atau tidak cukup diperbesar. Grafik ini menyaring data berdasarkan kenyataan:dalam kebanyakan kasus, jenis operasi ini akan menjadi penyisipan tunggal. Saya pikir saya akan membaginya berdasarkan tingkat kegagalan dan jenis disk tempat log digunakan, tetapi hanya melihat baris di mana kumpulan terdiri dari 20.000 sisipan individu.


Durasi, dalam milidetik, dikelompokkan menurut tingkat kegagalan dan lokasi log, untuk 20.000 sisipan individu

Di sini kita melihat bahwa semua pemicu AFTER rata-rata dalam rentang 10-11 detik (bergantung pada lokasi log), sementara semua pemicu INSTEAD OF berada jauh di bawah tanda 6 detik.

Kesimpulan

Sejauh ini, tampak jelas bagi saya bahwa BUKAN pemicu adalah pemenang dalam banyak kasus – dalam beberapa kasus lebih dari yang lain (misalnya, karena tingkat kegagalan naik). Faktor lain, seperti model pemulihan, tampaknya tidak terlalu berdampak pada kinerja secara keseluruhan.

Jika Anda memiliki ide lain tentang cara memecah data, atau ingin salinan data untuk melakukan pemotongan dan pemotongan Anda sendiri, beri tahu saya. Jika Anda membutuhkan bantuan untuk menyiapkan lingkungan ini sehingga Anda dapat menjalankan pengujian Anda sendiri, saya juga dapat membantu.

Sementara tes ini menunjukkan bahwa BUKAN pemicu pasti layak dipertimbangkan, itu bukan keseluruhan cerita. Saya benar-benar menyatukan pemicu ini menggunakan logika yang menurut saya paling masuk akal untuk setiap skenario, tetapi kode pemicu – seperti pernyataan T-SQL lainnya – dapat disetel untuk rencana yang optimal. Dalam postingan lanjutan, saya akan melihat potensi pengoptimalan yang dapat membuat pemicu AFTER lebih kompetitif.

Lampiran

Kueri yang digunakan untuk bagian Hasil:

Grafik 1 – Keseluruhan Agregat

SELECT RTRIM(l.loops) + ' x ' + RTRIM(l.perloop), AVG(r.Duration*1.0)
  FROM dbo.TestResults AS r
  INNER JOIN dbo.Loops AS l
  ON r.LoopID = l.LoopID
  GROUP BY RTRIM(l.loops) + ' x ' + RTRIM(l.perloop);
 
SELECT t.IsSnapshot, AVG(Duration*1.0)
  FROM dbo.TestResults AS tr
  INNER JOIN dbo.Tests AS t
  ON tr.TestID = t.TestID 
  GROUP BY t.IsSnapshot;
 
SELECT t.RecoveryModel, AVG(Duration*1.0)
  FROM dbo.TestResults AS tr
  INNER JOIN dbo.Tests AS t
  ON tr.TestID = t.TestID 
  GROUP BY t.RecoveryModel;
 
SELECT t.DiskLayout, AVG(Duration*1.0)
  FROM dbo.TestResults AS tr
  INNER JOIN dbo.Tests AS t
  ON tr.TestID = t.TestID 
  GROUP BY t.DiskLayout;
 
SELECT t.TriggerType, AVG(Duration*1.0)
  FROM dbo.TestResults AS tr
  INNER JOIN dbo.Tests AS t
  ON tr.TestID = t.TestID 
  GROUP BY t.TriggerType;
 
SELECT t.FailureRate, AVG(Duration*1.0)
  FROM dbo.TestResults AS tr
  INNER JOIN dbo.Tests AS t
  ON tr.TestID = t.TestID 
  GROUP BY t.FailureRate;

Grafik 2 &3 – 10 Terbaik &Terburuk

;WITH src AS 
(
    SELECT DiskLayout, RecoveryModel, TriggerType, FailureRate, IsSnapshot,
      Batch = RTRIM(l.loops) + ' x ' + RTRIM(l.perloop),
      Duration = AVG(Duration*1.0)
    FROM dbo.Tests AS t
    INNER JOIN dbo.TestResults AS tr
    ON tr.TestID = t.TestID 
    INNER JOIN dbo.Loops AS l
    ON tr.LoopID = l.LoopID
    GROUP BY DiskLayout, RecoveryModel, TriggerType, FailureRate, IsSnapshot,
      RTRIM(l.loops) + ' x ' + RTRIM(l.perloop)
),
agg AS
(
    SELECT label = REPLACE(REPLACE(DiskLayout,'Data',''),'_Log','/')
      + ', ' + RecoveryModel + ' recovery, ' + TriggerType
  	+ ', ' + RTRIM(FailureRate) + '% fail'
	+ ', Snapshot = ' + CASE IsSnapshot WHEN 1 THEN 'ON' ELSE 'OFF' END
  	+ ', ' + Batch + ' (ops x rows)',
      best10  = ROW_NUMBER() OVER (ORDER BY Duration), 
      worst10 = ROW_NUMBER() OVER (ORDER BY Duration DESC),
      Duration
    FROM src
)
SELECT grp, label, Duration FROM
(
  SELECT TOP (20) grp = 'best', label = RIGHT('0' + RTRIM(best10),2) + '. ' + label, Duration
    FROM agg WHERE best10 <= 10
    ORDER BY best10 DESC
  UNION ALL
  SELECT TOP (20) grp = 'worst', label = RIGHT('0' + RTRIM(worst10),2) + '. ' + label, Duration
    FROM agg WHERE worst10 <= 10
    ORDER BY worst10 DESC
  ) AS b
  ORDER BY grp;

Grafik 4 – Jenis Disk Log, Sisipan Tunggal

;WITH x AS
(
    SELECT 
      TriggerType,FailureRate,
      LogLocation = RIGHT(DiskLayout,3), 
      Duration = AVG(Duration*1.0)
    FROM dbo.TestResults AS tr
    INNER JOIN dbo.Tests AS t
    ON tr.TestID = t.TestID 
    INNER JOIN dbo.Loops AS l
    ON l.LoopID = tr.LoopID
    WHERE l.loops = 20000
    GROUP BY RIGHT(DiskLayout,3), FailureRate, TriggerType
)
SELECT TriggerType, FailureRate, 
  HDDDuration = MAX(CASE WHEN LogLocation = 'HDD' THEN Duration END),
  SSDDuration = MAX(CASE WHEN LogLocation = 'SSD' THEN Duration END)
FROM x 
GROUP BY TriggerType, FailureRate
ORDER BY TriggerType, FailureRate;

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Tidak ada bentuk caching basis data untuk mengurangi kueri basis data duplikat.

  2. Cara menggunakan Prisma

  3. Opsi Penyesuaian Kinerja Database Azure SQL

  4. FrankenQueries:ketika SQL dan NoSQL bertabrakan

  5. Alasan Lain untuk Menghindari sp_updatestats