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

Hekaton dengan twist:TVP dalam memori – Bagian 2

Dalam posting terakhir saya, saya menunjukkan bahwa pada volume kecil, TVP dengan memori yang dioptimalkan dapat memberikan manfaat kinerja yang substansial untuk pola kueri biasa.

Untuk menguji pada skala yang sedikit lebih tinggi, saya membuat salinan SalesOrderDetailEnlarged table, yang telah saya kembangkan menjadi sekitar 5.000.000 baris berkat skrip ini oleh Jonathan Kehayias (blog | @SQLPoolBoy)).

DROP TABLE dbo.SalesOrderDetailEnlarged;
GO
 
SELECT * INTO dbo.SalesOrderDetailEnlarged 
  FROM AdventureWorks2012.Sales.SalesOrderDetailEnlarged; -- 4,973,997 rows
 
CREATE CLUSTERED INDEX PK_SODE 
  ON dbo.SalesOrderDetailEnlarged(SalesOrderID, SalesOrderDetailID);

Saya juga membuat tiga versi dalam memori dari tabel ini, masing-masing dengan jumlah ember yang berbeda (memancing "titik manis") – 16.384, 131.072, dan 1.048.576. (Anda dapat menggunakan angka yang lebih bulat, tetapi angka tersebut akan dibulatkan ke pangkat 2 berikutnya.) Contoh:

CREATE TABLE [dbo].[SalesOrderDetailEnlarged_InMem_16K] -- and _131K and _1MM
(
	[SalesOrderID] [int] NOT NULL,
	[SalesOrderDetailID] [int] NOT NULL,
	[CarrierTrackingNumber] [nvarchar](25) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
	[OrderQty] [smallint] NOT NULL,
	[ProductID] [int] NOT NULL,
	[SpecialOfferID] [int] NOT NULL,
	[UnitPrice] [money] NOT NULL,
	[UnitPriceDiscount] [money] NOT NULL,
	[LineTotal] [numeric](38, 6) NOT NULL,
	[rowguid] [uniqueidentifier] NOT NULL,
	[ModifiedDate] [datetime] NOT NULL
 PRIMARY KEY NONCLUSTERED HASH 
 (
	[SalesOrderID],
	[SalesOrderDetailID]
 ) WITH ( BUCKET_COUNT = 16384) -- and 131072 and 1048576
) WITH ( MEMORY_OPTIMIZED = ON , DURABILITY = SCHEMA_AND_DATA );
GO
 
INSERT dbo.SalesOrderDetailEnlarged_InMem_16K
  SELECT * FROM dbo.SalesOrderDetailEnlarged;
 
INSERT dbo.SalesOrderDetailEnlarged_InMem_131K
  SELECT * FROM dbo.SalesOrderDetailEnlarged;
 
INSERT dbo.SalesOrderDetailEnlarged_InMem_1MM
  SELECT * FROM dbo.SalesOrderDetailEnlarged;
GO

Perhatikan saya mengubah ukuran ember dari contoh sebelumnya (256). Saat membuat tabel, Anda ingin memilih "titik manis" untuk ukuran bucket – Anda ingin mengoptimalkan indeks hash untuk pencarian titik, artinya Anda menginginkan bucket sebanyak mungkin dengan sesedikit mungkin baris di setiap bucket. Tentu saja jika Anda membuat ~5 juta ember (karena dalam kasus ini, mungkin bukan contoh yang sangat baik, ada ~5 juta kombinasi nilai yang unik), Anda akan memiliki beberapa pemanfaatan memori dan pertukaran pengumpulan sampah untuk ditangani. Namun jika Anda mencoba memasukkan ~5 juta nilai unik ke dalam 256 ember, Anda juga akan mengalami beberapa masalah. Bagaimanapun, diskusi ini melampaui lingkup pengujian saya untuk posting ini.

Untuk menguji terhadap tabel standar, saya membuat prosedur tersimpan yang serupa seperti pada pengujian sebelumnya:

CREATE PROCEDURE dbo.SODE_InMemory
  @InMemory dbo.InMemoryTVP READONLY
AS
BEGIN
  SET NOCOUNT ON;
 
  DECLARE @tn NVARCHAR(25);
 
  SELECT @tn = CarrierTrackingNumber
    FROM dbo.SalesOrderDetailEnlarged AS sode
    WHERE EXISTS (SELECT 1 FROM @InMemory AS t
    WHERE sode.SalesOrderID = t.Item);
END
GO
 
CREATE PROCEDURE dbo.SODE_Classic
  @Classic dbo.ClassicTVP READONLY
AS
BEGIN
  SET NOCOUNT ON;
 
  DECLARE @tn NVARCHAR(25);
 
  SELECT @tn = CarrierTrackingNumber
    FROM dbo.SalesOrderDetailEnlarged AS sode
    WHERE EXISTS (SELECT 1 FROM @Classic AS t
    WHERE sode.SalesOrderID = t.Item);
END
GO

Jadi pertama-tama, untuk melihat rencana, katakanlah, 1.000 baris dimasukkan ke dalam variabel tabel, lalu jalankan prosedurnya:

DECLARE @InMemory dbo.InMemoryTVP;
INSERT @InMemory SELECT TOP (1000) SalesOrderID
  FROM dbo.SalesOrderDetailEnlarged
  GROUP BY SalesOrderID ORDER BY NEWID();
 
DECLARE @Classic dbo.ClassicTVP;
INSERT @Classic SELECT Item FROM @InMemory;
 
EXEC dbo.SODE_Classic  @Classic  = @Classic;
EXEC dbo.SODE_InMemory @InMemory = @InMemory;

Kali ini, kita melihat bahwa dalam kedua kasus, pengoptimal telah memilih pencarian indeks berkerumun terhadap tabel dasar dan loop bersarang bergabung melawan TVP. Beberapa metrik biaya berbeda, tetapi sebaliknya, rencananya sangat mirip:

Paket serupa untuk TVP dalam memori vs. TVP klasik pada skala yang lebih tinggi

Membandingkan biaya operator pencarian – Klasik di sebelah kiri, Dalam Memori di sebelah kanan

Nilai absolut dari biaya membuatnya tampak seperti TVP klasik akan jauh lebih tidak efisien daripada TVP Dalam Memori. Tetapi saya bertanya-tanya apakah ini benar dalam praktiknya (terutama karena angka Perkiraan Jumlah Eksekusi di sebelah kanan tampak mencurigakan), jadi tentu saja, saya menjalankan beberapa tes. Saya memutuskan untuk memeriksa nilai 100, 1.000, dan 2.000 untuk dikirim ke prosedur.

DECLARE @values INT = 100; -- 1000, 2000
 
DECLARE @Classic dbo.ClassicTVP;
DECLARE @InMemory dbo.InMemoryTVP;
 
INSERT @Classic(Item) 
SELECT TOP (@values) SalesOrderID
  FROM dbo.SalesOrderDetailEnlarged
  GROUP BY SalesOrderID ORDER BY NEWID();
 
INSERT @InMemory(Item) SELECT Item FROM @Classic;
 
DECLARE @i INT = 1;
 
SELECT SYSDATETIME();
 
WHILE @i <= 10000
BEGIN
  EXEC dbo.SODE_Classic  @Classic  = @Classic;
  SET @i += 1;
END
 
SELECT SYSDATETIME();
 
SET @i = 1;
 
WHILE @i <= 10000
BEGIN
  EXEC dbo.SODE_InMemory @InMemory = @InMemory;
  SET @i += 1;
END
 
SELECT SYSDATETIME();

Hasil kinerja menunjukkan bahwa, pada jumlah pencarian titik yang lebih besar, menggunakan TVP Dalam Memori menyebabkan hasil yang sedikit berkurang, menjadi sedikit lebih lambat setiap saat:


Hasil 10.000 eksekusi menggunakan TVP klasik dan dalam memori

Jadi, bertentangan dengan kesan yang mungkin Anda ambil dari posting saya sebelumnya, menggunakan TVP dalam memori belum tentu bermanfaat dalam semua kasus.

Sebelumnya saya juga melihat prosedur tersimpan yang dikompilasi secara asli dan tabel dalam memori, dalam kombinasi dengan TVP dalam memori. Bisakah ini membuat perbedaan di sini? Spoiler:sama sekali tidak. Saya membuat tiga prosedur seperti ini:

CREATE PROCEDURE [dbo].[SODE_Native_InMem_16K] -- and _131K and _1MM
  @InMemory dbo.InMemoryTVP READONLY
WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER 
AS 
  BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'us_english');
 
  DECLARE @tn NVARCHAR(25);
 
  SELECT @tn = CarrierTrackingNumber
    FROM dbo.SalesOrderDetailEnlarged_InMem_16K AS sode -- and _131K and _1MM
    INNER JOIN @InMemory AS t -- no EXISTS allowed here
    ON sode.SalesOrderID = t.Item;
END
GO

Spoiler lain:Saya tidak dapat menjalankan 9 tes ini dengan jumlah iterasi 10.000 – terlalu lama. Alih-alih, saya mengulang dan menjalankan setiap prosedur 10 kali, menjalankan serangkaian tes itu 10 kali, dan mengambil rata-rata. Berikut adalah hasilnya:


Hasil dari 10 eksekusi menggunakan TVP dalam memori dan dikompilasi secara asli disimpan prosedur

Secara keseluruhan, eksperimen ini agak mengecewakan. Hanya dengan melihat besarnya perbedaan, dengan tabel di disk, panggilan prosedur tersimpan rata-rata diselesaikan dalam rata-rata 0,0036 detik. Namun, ketika semuanya menggunakan teknologi dalam memori, panggilan prosedur tersimpan rata-rata adalah 1,1662 detik. Aduh . Kemungkinan besar saya baru saja memilih kasus penggunaan yang buruk untuk didemonstrasikan secara keseluruhan, tetapi tampaknya pada saat itu merupakan "percobaan pertama" yang intuitif.

Kesimpulan

Ada banyak lagi untuk diuji di sekitar skenario ini, dan saya memiliki lebih banyak posting blog untuk diikuti. Saya belum mengidentifikasi kasus penggunaan optimal untuk TVP dalam memori pada skala yang lebih besar, tetapi berharap posting ini berfungsi sebagai pengingat bahwa meskipun solusi tampaknya optimal dalam satu kasus, tidak pernah aman untuk menganggapnya sama-sama berlaku untuk skenario yang berbeda. Inilah tepatnya bagaimana OLTP Dalam Memori harus didekati:sebagai solusi dengan serangkaian kasus penggunaan sempit yang mutlak harus divalidasi sebelum diimplementasikan dalam produksi.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Kueri Basis Data:Bagaimana Cara Menemukan Jarum di Tumpukan Jerami?

  2. Model Data Bengkel Mobil

  3. Apa Kegunaan Pernyataan SQL GROUP BY?

  4. Cara Mengklasifikasikan, Menemukan, dan Menyamarkan PII di Basis Data…

  5. Persamaan dan Perbedaan Fungsi RANK, DENSE_RANK dan ROW_NUMBER