Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Chuyển đổi ngầm bên cột đắt như thế nào?

Trong tháng trước, tôi đã tương tác với nhiều khách hàng có vấn đề chuyển đổi ngầm bên cột liên quan đến khối lượng công việc OLTP của họ. Trong hai trường hợp, tác động tích lũy của các chuyển đổi ngầm bên cột là nguyên nhân cơ bản của vấn đề hiệu suất tổng thể đối với Máy chủ SQL đang được xem xét và rất tiếc không có tùy chọn cấu hình hoặc cài đặt kỳ diệu nào mà chúng tôi có thể điều chỉnh để cải thiện tình hình khi đây là trường hợp. Mặc dù chúng tôi có thể đưa ra các đề xuất để khắc phục các kết quả thấp hơn khác có thể đang ảnh hưởng đến hiệu suất tổng thể, nhưng tác động của các chuyển đổi ngầm bên cột là thứ yêu cầu thay đổi thiết kế giản đồ để khắc phục hoặc thay đổi mã để ngăn cột- chuyển đổi bên so với hoàn toàn so với lược đồ cơ sở dữ liệu hiện tại.

Chuyển đổi ngầm định là kết quả của công cụ cơ sở dữ liệu so sánh các giá trị của các kiểu dữ liệu khác nhau trong quá trình thực thi truy vấn. Bạn có thể tìm thấy danh sách các chuyển đổi ngầm định có thể xảy ra bên trong công cụ cơ sở dữ liệu trong chủ đề Sách Trực tuyến về Chuyển đổi Kiểu Dữ liệu (Công cụ Cơ sở dữ liệu). Chuyển đổi ngầm định luôn xảy ra dựa trên mức độ ưu tiên của loại dữ liệu đối với các loại dữ liệu đang được so sánh trong quá trình hoạt động. Thứ tự ưu tiên kiểu dữ liệu có thể được tìm thấy trong chủ đề Sách Trực tuyến Thứ tự ưu tiên kiểu dữ liệu (Giao dịch-SQL). Gần đây, tôi đã viết blog về các chuyển đổi ngầm dẫn đến việc quét chỉ mục và cung cấp các biểu đồ có thể được sử dụng để xác định các chuyển đổi ngầm có vấn đề nhất.

Thiết lập Kiểm tra

Để chứng minh chi phí hiệu suất được liên kết với các chuyển đổi ngầm bên cột dẫn đến quét chỉ mục, tôi đã chạy một loạt các thử nghiệm khác nhau dựa trên cơ sở dữ liệu AdventureWorks2012 bằng cách sử dụng bảng Sales.SalesOrderDetail để xây dựng các bảng thử nghiệm và tập dữ liệu. Chuyển đổi ngầm bên cột phổ biến nhất mà tôi thấy với tư cách là nhà tư vấn xảy ra khi loại cột là char hoặc varchar và mã ứng dụng chuyển một tham số là nchar hoặc nvarchar và bộ lọc trên cột char hoặc varchar. Để mô phỏng loại kịch bản này, tôi đã tạo một bản sao của bảng SalesOrderDetail (có tên là SalesOrderDetail_ASCII) và thay đổi cột CarrierTrackingNumber từ nvarchar thành varchar. Ngoài ra, tôi đã thêm chỉ mục không hợp nhất trên cột CarrierTrackingNumber vào bảng SalesOrderDetail ban đầu, cũng như bảng SalesOrderDetail_ASCII mới.

USE [AdventureWorks2012]
GO
-- Add CarrierTrackingNumber index to original Sales.SalesOrderDetail table
IF NOT EXISTS 
(
  SELECT 1 FROM sys.indexes 
    WHERE [object_id] = OBJECT_ID(N'Sales.SalesOrderDetail') 
    AND name=N'IX_SalesOrderDetail_CarrierTrackingNumber'
)
BEGIN
  CREATE INDEX IX_SalesOrderDetail_CarrierTrackingNumber 
    ON Sales.SalesOrderDetail (CarrierTrackingNumber);
END
GO
 
IF OBJECT_ID('Sales.SalesOrderDetail_ASCII') IS NOT NULL
BEGIN
  DROP TABLE Sales.SalesOrderDetail_ASCII;
END
GO
 
CREATE TABLE Sales.SalesOrderDetail_ASCII
(
  SalesOrderID int NOT NULL,
  SalesOrderDetailID int NOT NULL IDENTITY (1, 1),
  CarrierTrackingNumber varchar(25) NULL,
  OrderQty smallint NOT NULL,
  ProductID int NOT NULL,
  SpecialOfferID int NOT NULL,
  UnitPrice money NOT NULL,
  UnitPriceDiscount money NOT NULL,
  LineTotal  AS (isnull(([UnitPrice]*((1.0)-[UnitPriceDiscount]))*[OrderQty],(0.0))),
  rowguid uniqueidentifier NOT NULL ROWGUIDCOL,
  ModifiedDate datetime NOT NULL
);
GO
 
SET IDENTITY_INSERT Sales.SalesOrderDetail_ASCII ON;
GO
 
INSERT INTO Sales.SalesOrderDetail_ASCII
(
  SalesOrderID, SalesOrderDetailID, CarrierTrackingNumber, 
  OrderQty, ProductID, SpecialOfferID, UnitPrice, 
  UnitPriceDiscount, rowguid, ModifiedDate
)
SELECT 
  SalesOrderID, SalesOrderDetailID, CONVERT(varchar(25), CarrierTrackingNumber),  
  OrderQty, ProductID, SpecialOfferID, UnitPrice, 
  UnitPriceDiscount, rowguid, ModifiedDate 
FROM Sales.SalesOrderDetail WITH (HOLDLOCK TABLOCKX);
GO
 
SET IDENTITY_INSERT Sales.SalesOrderDetail_ASCII OFF;
GO
 
ALTER TABLE Sales.SalesOrderDetail_ASCII ADD CONSTRAINT
  PK_SalesOrderDetail_ASCII_SalesOrderID_SalesOrderDetailID 
  PRIMARY KEY CLUSTERED (SalesOrderID, SalesOrderDetailID);
 
CREATE UNIQUE NONCLUSTERED INDEX AK_SalesOrderDetail_ASCII_rowguid 
  ON Sales.SalesOrderDetail_ASCII (rowguid);
 
CREATE NONCLUSTERED INDEX IX_SalesOrderDetail_ASCII_ProductID 
  ON Sales.SalesOrderDetail_ASCII (ProductID);
 
CREATE INDEX IX_SalesOrderDetail_ASCII_CarrierTrackingNumber 
  ON Sales.SalesOrderDetail_ASCII (CarrierTrackingNumber);
GO

Bảng SalesOrderDetail_ASCII mới có 121.317 hàng và có kích thước 17,5 MB và sẽ được sử dụng để đánh giá tổng chi phí của một bảng nhỏ. Tôi cũng đã tạo một bảng lớn hơn mười lần, bằng cách sử dụng phiên bản sửa đổi của tập lệnh Phóng to Cơ sở dữ liệu mẫu AdventureWorks từ blog của tôi, chứa 1.334.487 hàng và có kích thước 190 MB. Máy chủ thử nghiệm cho điều này là cùng một VM 4 vCPU với RAM 4GB, chạy Windows Server 2008 R2 và SQL Server 2012, với Gói dịch vụ 1 và Cập nhật tích lũy 3, mà tôi đã sử dụng trong các bài viết trước, vì vậy các bảng sẽ hoàn toàn phù hợp trong bộ nhớ , loại bỏ chi phí I / O của đĩa khỏi ảnh hưởng đến các thử nghiệm đang chạy.

Khối lượng công việc thử nghiệm được tạo bằng cách sử dụng một loạt các tập lệnh PowerShell chọn danh sách CarrierTrackingNumbers từ bảng SalesOrderDetail, xây dựng một ArrayList, sau đó chọn ngẫu nhiên một CarrierTrackingNumber từ ArrayList để truy vấn bảng SalesOrderDetail_ASCII bằng cách sử dụng tham số varchar và sau đó là tham số nvarchar, và sau đó để truy vấn bảng SalesOrderDetail bằng cách sử dụng tham số nvarchar để cung cấp so sánh trong đó cột và tham số đều là nvarchar. Mỗi bài kiểm tra riêng lẻ chạy câu lệnh 10.000 lần để cho phép đo lường chi phí hiệu suất trong một khối lượng công việc liên tục.

#No Implicit Conversions
$loop = 10000;
Write-Host "Small table no conversion start time:"
[DateTime]::Now
$query = @"SELECT * FROM Sales.SalesOrderDetail_ASCII "
          "WHERE CarrierTrackingNumber = @CTNumber;";
while($loop -gt 0)
{
  $Value = Get-Random -InputObject $Results;
  $SqlCmd = $SqlConn.CreateCommand();
  $SqlCmd.CommandText = $query;
  $SqlCmd.CommandType = [System.Data.CommandType]::Text;
 
  $SqlParameter = $SqlCmd.Parameters.AddWithValue("@CTNumber", $Value);
  $SqlParameter.SqlDbType = [System.Data.SqlDbType]::VarChar;
  $SqlParameter.Size = 30;
 
  $SqlCmd.ExecuteNonQuery() | Out-Null;
  $loop--;
}
Write-Host "Small table no conversion end time:"
[DateTime]::Now
 
Sleep -Seconds 10;
 
#Small table implicit conversions
$loop = 10000;
Write-Host "Small table implicit conversions start time:"
[DateTime]::Now
$query = @"SELECT * FROM Sales.SalesOrderDetail_ASCII "
          "WHERE CarrierTrackingNumber = @CTNumber;";
while($loop -gt 0)
{
  $Value = Get-Random -InputObject $Results;	
  $SqlCmd = $SqlConn.CreateCommand();
  $SqlCmd.CommandText = $query;
  $SqlCmd.CommandType = [System.Data.CommandType]::Text;
 
  $SqlParameter = $SqlCmd.Parameters.AddWithValue("@CTNumber", $Value);
  $SqlParameter.SqlDbType = [System.Data.SqlDbType]::NVarChar;
  $SqlParameter.Size = 30;
 
  $SqlCmd.ExecuteNonQuery() | Out-Null;
  $loop--;
}
Write-Host "Small table implicit conversions end time:"
[DateTime]::Now
 
Sleep -Seconds 10;
 
#Small table unicode no implicit conversions
$loop = 10000;
Write-Host "Small table unicode no implicit conversion start time:"
[DateTime]::Now
$query = @"SELECT * FROM Sales.SalesOrderDetail "
          "WHERE CarrierTrackingNumber = @CTNumber;"
while($loop -gt 0)
{
  $Value = Get-Random -InputObject $Results;	
  $SqlCmd = $SqlConn.CreateCommand();
  $SqlCmd.CommandText = $query;
  $SqlCmd.CommandType = [System.Data.CommandType]::Text;
 
  $SqlParameter = $SqlCmd.Parameters.AddWithValue("@CTNumber", $Value);
  $SqlParameter.SqlDbType = [System.Data.SqlDbType]::NVarChar;
  $SqlParameter.Size = 30;
 
  $SqlCmd.ExecuteNonQuery() | Out-Null;
  $loop--;
}
Write-Host "Small table unicode no implicit conversion end time:"
[DateTime]::Now

Nhóm thử nghiệm thứ hai đã được chạy trên bảng SalesOrderDetailEnlarged_ASCII và SalesOrderDetailEnlarged bằng cách sử dụng cùng một tham số như nhóm thử nghiệm đầu tiên để hiển thị sự khác biệt chi phí khi kích thước của dữ liệu được lưu trữ trong bảng tăng lên theo thời gian. Một bộ thử nghiệm cuối cùng cũng được chạy dựa trên bảng SalesOrderDetail bằng cách sử dụng cột ProductID làm cột lọc với các loại tham số là int, bigint và sau đó là smallint để cung cấp so sánh chi phí chuyển đổi ngầm định không dẫn đến quét chỉ mục để so sánh.

Lưu ý:Tất cả các tập lệnh được đính kèm với bài viết này để cho phép sao chép các thử nghiệm chuyển đổi ngầm để đánh giá và so sánh thêm.

Kết quả kiểm tra

Trong mỗi lần thực thi thử nghiệm, Bộ giám sát hiệu suất đã được định cấu hình để chạy Bộ thu thập dữ liệu bao gồm Bộ xử lý \% Thời gian xử lý và máy chủ SQL:SQLStatisitics \ Batch Request / bộ đếm giây để theo dõi chi phí hiệu suất cho từng thử nghiệm. Ngoài ra, Sự kiện mở rộng đã được định cấu hình để theo dõi sự kiện rpc_completed nhằm cho phép theo dõi thời lượng trung bình, cpu_time và số lần đọc logic cho mỗi bài kiểm tra.

Kết quả CarrierTrackingNumber ở bảng nhỏ


Hình 1 - Biểu đồ giám sát hiệu suất của bộ đếm

TestID Kiểu dữ liệu cột Kiểu dữ liệu tham số % thời gian trung bình của bộ xử lý Yêu cầu hàng loạt trung bình / giây Thời lượng h:mm:ss
1 Varchar Varchar 2,5 192.3 0:00:51
2 Varchar Nvarchar 19,4 46,7 0:03:33
3 Nvarchar Nvarchar 2.6 192.3 0:00:51

Bảng 2 - Mức trung bình của dữ liệu Giám sát hiệu suất

Từ kết quả, chúng ta có thể thấy rằng chuyển đổi ngầm bên cột từ varchar thành nvarchar và kết quả quét chỉ mục có tác động đáng kể đến hiệu suất của khối lượng công việc. % Thời gian bộ xử lý trung bình cho kiểm tra chuyển đổi ngầm bên cột (TestID =2) nhiều hơn gần mười lần so với các thử nghiệm khác trong đó chuyển đổi ngầm bên cột, dẫn đến quét chỉ mục, không xảy ra. Ngoài ra, Yêu cầu hàng loạt trung bình / giây cho thử nghiệm chuyển đổi ngầm bên cột chỉ dưới 25% so với các thử nghiệm khác. Thời lượng của các thử nghiệm trong đó các chuyển đổi ngầm không xảy ra đều mất 51 giây, ngay cả khi dữ liệu được lưu trữ dưới dạng nvarchar trong thử nghiệm số 3 bằng cách sử dụng kiểu dữ liệu nvarchar, yêu cầu dung lượng lưu trữ gấp đôi. Điều này được mong đợi vì bảng vẫn nhỏ hơn vùng đệm.

TestID cpu_time trung bình (µs) Thời lượng trung bình (µs) Logic_reads trung bình
1 40,7 154,9 51,6
2 15.640,8 15.760.0 385,6
3 45,3 169,7 52,7

Bảng 3 - Mức trung bình của Sự kiện mở rộng

Dữ liệu được thu thập bởi sự kiện rpc_completed trong Sự kiện mở rộng cho thấy rằng cpu_time, thời lượng và số lần đọc logic trung bình được liên kết với các truy vấn không thực hiện chuyển đổi ngầm bên cột là gần tương đương, trong đó chuyển đổi ngầm bên cột sinh ra một CPU đáng kể tổng chi phí, cũng như thời lượng trung bình dài hơn với số lần đọc hợp lý hơn đáng kể.

Kết quả CarrierTrackingNumber trong bảng được phóng to


Hình 4 - Biểu đồ giám sát hiệu suất của bộ đếm

TestID Kiểu dữ liệu cột Kiểu dữ liệu tham số % thời gian trung bình của bộ xử lý Yêu cầu hàng loạt trung bình / giây Thời lượng h:mm:ss
1 Varchar Varchar 7.2 164.0 0:01:00
2 Varchar Nvarchar 83,8 15,4 0:10:49
3 Nvarchar Nvarchar 7.0 166,7 0:01:00

Bảng 5 - Mức trung bình của dữ liệu Giám sát hiệu suất

Khi kích thước của dữ liệu tăng lên, chi phí hiệu suất của chuyển đổi ngầm bên cột cũng tăng lên. % Thời gian xử lý trung bình cho kiểm tra chuyển đổi ngầm bên cột (TestID =2), một lần nữa, gần gấp mười lần so với các thử nghiệm khác trong đó chuyển đổi ngầm bên cột dẫn đến quét chỉ mục, không xảy ra. Ngoài ra, Yêu cầu hàng loạt trung bình / giây cho thử nghiệm chuyển đổi ngầm bên cột chỉ dưới 10% so với các thử nghiệm khác. Thời lượng của các bài kiểm tra trong đó cả hai chuyển đổi ngầm không xảy ra đều mất một phút, trong khi kiểm tra chuyển đổi ngầm bên cột yêu cầu gần mười một phút để thực hiện.

TestID cpu_time trung bình (µs) Thời lượng trung bình (µs) Logic_reads trung bình
1 728,5 1.036,5 569,6
2 214.174,6 59.519,1 4.358,2
3 821,5 1.032,4 553,5

Bảng 6 - Mức trung bình của Sự kiện mở rộng

Kết quả Sự kiện mở rộng thực sự bắt đầu hiển thị chi phí hiệu suất do các chuyển đổi ngầm bên cột gây ra cho khối lượng công việc. Cpu_time trung bình cho mỗi lần thực thi tăng lên hơn 214ms và gấp hơn 200 lần cpu_time cho các câu lệnh không có chuyển đổi ngầm bên cột. Thời lượng cũng gần 60 lần so với các câu lệnh không có chuyển đổi ngầm bên cột.

Tóm tắt

Khi kích thước của dữ liệu tiếp tục tăng, chi phí liên quan đến chuyển đổi ngầm bên cột dẫn đến quét chỉ mục cho khối lượng công việc cũng sẽ tiếp tục tăng và điều quan trọng cần nhớ là tại một số thời điểm, không có số lượng phần cứng sẽ có thể đối phó với chi phí hiệu suất. Chuyển đổi ngầm định là một điều dễ dàng để ngăn chặn khi tồn tại một thiết kế lược đồ cơ sở dữ liệu tốt và các nhà phát triển tuân theo các kỹ thuật mã hóa ứng dụng tốt. Trong các tình huống mà thực tiễn mã hóa ứng dụng dẫn đến việc tham số hóa tận dụng tham số hóa nvarchar, tốt hơn là bạn nên so khớp thiết kế lược đồ cơ sở dữ liệu với tham số hóa truy vấn hơn là sử dụng các cột varchar trong thiết kế cơ sở dữ liệu và gánh chịu chi phí hiệu suất từ ​​chuyển đổi ngầm bên cột.

Tải xuống tập lệnh demo:Implicit_Conversion_Tests.zip (5 KB)


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Giám sát hiệu suất cho TimescaleDB

  2. Điều chỉnh hiệu suất Knee-Jerk:Chỉ cần thêm ổ SSD

  3. Các nguyên tắc cơ bản về biểu thức bảng, Phần 1

  4. Sử dụng ODBC với Dịch vụ Đăng nhập Một lần của Salesforce và Active Directory (ADFS) (SSO)

  5. SQL MIN () cho người mới bắt đầu