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

Bất ngờ về Hiệu suất và Giả định:STRING_SPLIT ()

Hơn ba năm trước, tôi đã đăng một loạt ba phần về tách chuỗi:

  • Tách chuỗi đúng cách - hoặc cách tốt nhất tiếp theo
  • Tách chuỗi:Theo dõi
  • Tách chuỗi:Giờ đây với ít T-SQL hơn

Sau đó vào tháng 1, tôi giải quyết một vấn đề phức tạp hơn một chút:

  • So sánh các phương pháp tách / nối chuỗi

Trong suốt, kết luận của tôi là: NGỪNG LÀM VIỆC NÀY TRONG T-SQL . Sử dụng CLR hoặc tốt hơn là chuyển các tham số có cấu trúc như DataTables từ ứng dụng của bạn sang các tham số có giá trị bảng (TVP) trong quy trình của bạn, tránh hoàn toàn tất cả việc xây dựng và giải cấu trúc chuỗi - đây thực sự là một phần của giải pháp gây ra các vấn đề về hiệu suất.

Và sau đó SQL Server 2016 ra đời…

Khi RC0 được phát hành, một chức năng mới đã được ghi lại mà không có nhiều sự phô trương:STRING_SPLIT . Một ví dụ nhanh:

 SELECT * FROM STRING_SPLIT ('a, b, cd', ','); / * kết quả:giá trị -------- a b cd * / 

Nó đã lọt vào mắt xanh của một số đồng nghiệp, bao gồm Dave Ballantyne, người đã viết về các tính năng chính - nhưng đủ tốt để cho tôi quyền từ chối đầu tiên khi so sánh hiệu suất.

Đây chủ yếu là một bài tập học thuật, bởi vì với một loạt các hạn chế trong lần lặp đầu tiên của tính năng, nó có lẽ sẽ không khả thi đối với một số lượng lớn các trường hợp sử dụng. Đây là danh sách các quan sát mà Dave và tôi đã thực hiện, một số trong số đó có thể là yếu tố phá vỡ thỏa thuận trong một số trường hợp nhất định:

  • chức năng yêu cầu cơ sở dữ liệu phải ở mức tương thích 130;
  • nó chỉ chấp nhận các dấu phân cách có một ký tự;
  • không có cách nào để thêm các cột đầu ra (như cột chỉ ra vị trí thứ tự trong chuỗi);
    • liên quan, không có cách nào để kiểm soát việc sắp xếp - các tùy chọn duy nhất là tùy ý và theo thứ tự bảng chữ cái ORDER BY value;
  • cho đến nay, nó luôn ước tính 50 hàng đầu ra;
  • khi sử dụng nó cho DML, trong nhiều trường hợp, bạn sẽ nhận được một ống đệm (để bảo vệ Hallowe'en);
  • NULL đầu vào dẫn đến kết quả trống;
  • không có cách nào để đẩy các vị từ xuống, chẳng hạn như loại bỏ các bản sao hoặc các chuỗi trống do các dấu phân cách liên tiếp;
  • không có cách nào để thực hiện các hoạt động đối với các giá trị đầu ra cho đến khi thực tế (ví dụ:nhiều hàm phân tách thực hiện LTRIM/RTRIM hoặc chuyển đổi rõ ràng cho bạn - STRING_SPLIT loại bỏ tất cả những thứ xấu xí, chẳng hạn như khoảng trắng ở đầu).

Vì vậy, với những hạn chế đó, chúng ta có thể chuyển sang một số thử nghiệm hiệu suất. Với thành tích theo dõi của Microsoft với các chức năng tích hợp tận dụng CLR bên dưới ( ho FORMAT() ho ), Tôi đã nghi ngờ về việc liệu chức năng mới này có thể tiến gần đến các phương pháp nhanh nhất mà tôi đã thử nghiệm cho đến nay hay không.

Hãy sử dụng trình phân tách chuỗi để phân tách các chuỗi số được phân tách bằng dấu phẩy, theo cách này, người bạn mới của chúng ta JSON cũng có thể tham gia và chơi. Và chúng tôi sẽ nói rằng không có danh sách nào có thể vượt quá 8.000 ký tự, vì vậy không có MAX các loại là bắt buộc và vì chúng là số nên chúng ta không phải xử lý bất cứ thứ gì kỳ lạ như Unicode.

Đầu tiên, hãy tạo các hàm của chúng ta, một số hàm mà tôi đã điều chỉnh từ bài viết đầu tiên ở trên. Tôi đã bỏ đi một cặp đôi mà tôi không cảm thấy sẽ cạnh tranh; Tôi sẽ để nó như một bài tập cho người đọc để kiểm tra những điều đó.

    Bảng số

    Cái này một lần nữa cần một số thiết lập, nhưng nó có thể là một bảng khá nhỏ do những hạn chế giả tạo mà chúng tôi đang đặt:

     ĐẶT SỐ TÀI KHOẢN BẬT; DECLARE @UpperLimit INT =8000;; VỚI n AS (SELECT x =ROW_NUMBER () OVER (ORDER BY s1. [Object_id]) FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2) SELECT Number =x INTO dbo.Các số TỪ n WHERE x GIỮA 1 VÀ @UpperLimit; CHỈ SỐ ĐƯỢC ĐIỀU CHỈNH DUY NHẤT CỦA GOCREATE n TRÊN dbo.Numbers (Số); 

    Sau đó, hàm:

     TẠO CHỨC NĂNG dbo.SplitStrings_Numbers (@List varchar (8000), @Delimiter char (1)) BẢNG QUAY LẠI VỚI SCHEMABINDINGAS RETURN (SELECT [Value] =SUBSTRING (@List, [Number], CHARINDEX (@Delimiter, @List) + @Delimiter, [Number]) - [Number]) FROM dbo.Numbers WHERE Number <=LEN (@List) AND SUBSTRING (@Delimiter + @List, [Number], 1) =@Delimiter); 

    JSON

    Dựa trên một cách tiếp cận được nhóm công cụ lưu trữ tiết lộ lần đầu, tôi đã tạo một trình bao bọc tương tự xung quanh OPENJSON , chỉ cần lưu ý rằng dấu phân cách phải là dấu phẩy trong trường hợp này hoặc bạn phải thực hiện một số thay thế chuỗi tác vụ nặng trước khi chuyển giá trị vào hàm gốc:

     TẠO CHỨC NĂNG dbo.SplitStrings_JSON (@List varchar (8000), @Delimiter char (1) - bị bỏ qua nhưng giúp kiểm tra tự động dễ dàng hơn) BẢNG QUAY LẠI VỚI SCHEMABINDINGAS RETURN (CHỌN giá trị TỪ OPENJSON (CHAR (91) + @List +) CHAR (93))); 

    CHAR (91) / CHAR (93) chỉ thay thế [và] tương ứng do các vấn đề về định dạng.

    XML

     TẠO CHỨC NĂNG dbo.SplitStrings_XML (@List varchar (8000), @Delimiter char (1)) BẢNG TRỞ LẠI VỚI SCHEMABINDINGAS RETURN (SELECT [value] =y.i.value ('(./ text ()) [1]', 'varchar (8000)') FROM (SELECT x =CONVERT (XML, '' + REPLACE (@List, @Delimiter, ' ') + '') .query ('.')) ÁP DỤNG LÀM CHÉO X.nodes ('i') NHƯ y (i)); 

    CLR

    Tôi một lần nữa mượn mã phân tách đáng tin cậy của Adam Machanic từ gần bảy năm trước, mặc dù nó hỗ trợ Unicode, MAX loại và dấu phân cách nhiều ký tự (và thực ra, vì tôi không muốn làm rối mã hàm một chút nào, điều này giới hạn chuỗi đầu vào của chúng tôi ở 4.000 ký tự thay vì 8.000):

     TẠO CHỨC NĂNG dbo.SplitStrings_CLR (@List nvarchar (MAX), @Delimiter nvarchar (255)) BẢNG QUAY LẠI (giá trị nvarchar (4000)) TÊN BÊN NGOÀI CLRUtilities.UserDefinedFunctions.SplitString_Multi; 

    STRING_SPLIT

    Chỉ để nhất quán, tôi đặt một trình bao bọc xung quanh STRING_SPLIT :

     TẠO CHỨC NĂNG dbo.SplitStrings_Native (@List varchar (8000), @Delimiter char (1)) BẢNG TRỞ LẠI VỚI SCHEMABINDINGAS RETURN (CHỌN giá trị TỪ STRING_SPLIT (@List, @Delimiter)); 

Dữ liệu nguồn &Kiểm tra tình trạng

Tôi đã tạo bảng này để dùng làm nguồn chuỗi đầu vào cho các hàm:

 TẠO BẢNG dbo.SourceTable (RowNum int IDENTITY (1,1) PRIMARY KEY, StringValue varchar (8000));; WITH x AS (SELECT TOP (60000) x =STUFF ((SELECT TOP (ABS (o. [Object_id]% 20)) ',' + CONVERT (varchar (12), c. [Object_id]) FROM sys.all_columns AS c WHERE c. [Object_id]  

Chỉ để tham khảo, hãy xác thực rằng 50.000 hàng được đưa vào bảng và kiểm tra độ dài trung bình của chuỗi và số phần tử trung bình trên mỗi chuỗi:

 SELECT [Values] =COUNT (*), AvgStringLength =AVG (1.0 * LEN (StringValue)), AvgElementCount =AVG (1.0 * LEN (StringValue) -LEN (REPLACE (StringValue, ',', '')) ) TỪ dbo.SourceTable; / * kết quả:Giá trị AvgStringLength AbgElementCount ------------------------------- 50000 108.476380 8.911840 * /  

Và cuối cùng, hãy đảm bảo mỗi hàm trả về dữ liệu phù hợp cho bất kỳ RowNum nào đã cho , vì vậy chúng tôi sẽ chỉ chọn một cách ngẫu nhiên và so sánh các giá trị thu được qua mỗi phương pháp. Tất nhiên, kết quả của bạn sẽ khác nhau.

 SELECT f.value FROM dbo.SourceTable AS s CROSS ÁP DỤNG phương thức dbo.SplitStrings _ / * * / (s.StringValue, ',') AS f WHERE s.RowNum =37219 ĐẶT HÀNG THEO f.value; 

Chắc chắn, tất cả các hàm hoạt động như mong đợi (sắp xếp không phải là số; hãy nhớ rằng, các hàm xuất chuỗi):

Tập hợp đầu ra mẫu từ mỗi hàm

Kiểm tra hiệu suất

 SELECT SYSDATETIME (); GODECLARE @x VARCHAR (8000); SELECT @x =f.value FROM dbo.SourceTable AS s CROSS ÁP DỤNG dbo.SplitStrings _ / * method * / (s.StringValue, ',') AS f; ĐI 100SELECT SYSDATETIME (); 

Tôi đã chạy đoạn mã trên 10 lần cho mỗi phương pháp và tính thời gian trung bình cho mỗi phương pháp. Và đây là nơi mà bất ngờ đã đến với tôi. Do những hạn chế trong STRING_SPLIT gốc , giả định của tôi là nó được kết hợp với nhau một cách nhanh chóng, và hiệu suất đó sẽ cho thấy sự tin cậy. Cậu bé là kết quả khác với những gì tôi mong đợi:

Thời lượng trung bình của STRING_SPLIT so với các phương pháp khác

Cập nhật 2016-03-20

Dựa trên câu hỏi bên dưới của Lars, tôi đã chạy lại các bài kiểm tra với một số thay đổi:

  • Tôi đã theo dõi phiên bản của mình với SQL Sentry Performance Advisor để nắm bắt cấu hình CPU trong quá trình kiểm tra;
  • Tôi đã ghi lại số liệu thống kê về thời gian chờ cấp phiên giữa mỗi đợt;
  • Tôi đã chèn thời gian trễ giữa các đợt để hoạt động sẽ rõ ràng trực quan trên trang tổng quan của Cố vấn hiệu suất.

Tôi đã tạo một bảng mới để nắm bắt thông tin chỉ số chờ:

 TẠO BẢNG dbo.Timings (dt datetime, test varchar (64), point varchar (64), session_id smallint, wait_type nvarchar (60), wait_time_ms bigint,); 

Sau đó, mã cho mỗi bài kiểm tra được thay đổi thành sau:

 CHỜ TRÌ HOÃN '00:00:30'; DECLARE @d DATETIME =SYSDATETIME (); CHÈN dbo.Timings (dt, test, point, wait_type, wait_time_ms) CHỌN @d, test =/ * 'method' * /, point ='Start', wait_type, wait_time_msFROM sys.dm_exec_session_wait_stats WHERE session_id =@@ SPID; ĐI DECLARE @x VARCHAR (8000); SELECT @x =f.value FROM dbo.SourceTable AS s CROSS ÁP DỤNG dbo.SplitStrings _ / * method * / (s.StringValue, ',') AS fGO 100 DECLARE @d DATETIME =SYSDATETIME (); CHÈN dbo.Timings (dt, test, point, wait_type, wait_time_ms) CHỌN @d, / * 'method' * /, 'End', wait_type, wait_time_msFROM sys.dm_exec_session_wait_stats WHERE session_id =@@ SPID; 

Tôi đã chạy thử nghiệm và sau đó chạy các truy vấn sau:

 - xác nhận rằng thời gian ở trong cùng một quả bóng với các bài kiểm tra trước đó Kiểm tra lựa chọn, DATEDIFF (SECOND, MIN (dt), MAX (dt)) FROM dbo.Timings WITH (NOLOCK) GROUP BY test ORDER BY 2 DESC; - xác định cửa sổ để áp dụng cho bảng điều khiển Cố vấn Hiệu suấtSELECT MIN (dt), MAX (dt) FROM dbo.Timings; - nhận số liệu thống kê về thời gian chờ đã đăng ký cho mỗi lần kiểm tra sessionSELECT, wait_type, delta FROM (SELECT f.test, rn =RANK () OVER (PARTITION BY f.point ORDER BY f.dt), f.wait_type, delta =f.wait_time_ms - COALESCE (s.wait_time_ms, 0) TỪ dbo.Timings AS f LEFT OUTER THAM GIA dbo.Timings AS s ON s.test =f.test AND s.wait_type =f.wait_type AND s.point ='Start' WHERE f.point ='End') AS x WHERE delta> 0ORDER BY rn, delta DESC; 

Từ truy vấn đầu tiên, thời gian vẫn nhất quán với các thử nghiệm trước đó (tôi sẽ lập biểu đồ lại nhưng điều đó sẽ không tiết lộ bất kỳ điều gì mới).

Từ truy vấn thứ hai, tôi có thể làm nổi bật phạm vi này trên trang tổng quan Cố vấn hiệu suất và từ đó dễ dàng xác định từng lô:

Các lô được chụp trên biểu đồ CPU trên trang tổng quan Trình tư vấn hiệu suất

Rõ ràng, tất cả các phương pháp * ngoại trừ * STRING_SPLIT đã chốt một lõi đơn trong suốt thời gian thử nghiệm (đây là máy lõi tứ và CPU ổn định ở mức 25%). Có khả năng là Lars đang nói bóng gió bên dưới STRING_SPLIT đó nhanh hơn với chi phí đập CPU, nhưng có vẻ như đây không phải là trường hợp.

Cuối cùng, từ truy vấn thứ ba, tôi có thể thấy số liệu thống kê chờ sau được tích lũy sau mỗi đợt:

Chờ mỗi phiên, tính bằng mili giây

Các lần chờ được DMV ghi lại không giải thích đầy đủ thời lượng của các truy vấn, nhưng chúng phục vụ cho việc hiển thị nơi bổ sung sự chờ đợi được phát sinh.

Kết luận

Mặc dù CLR tùy chỉnh vẫn cho thấy lợi thế lớn so với các phương pháp tiếp cận T-SQL truyền thống và việc sử dụng JSON cho chức năng này dường như không có gì khác hơn là một sự mới lạ, STRING_SPLIT là người chiến thắng rõ ràng - một dặm. Vì vậy, nếu bạn chỉ cần tách một chuỗi và có thể giải quyết tất cả các hạn chế của nó, có vẻ như đây là một lựa chọn khả thi hơn nhiều so với tôi mong đợi. Hy vọng rằng trong các bản dựng trong tương lai, chúng ta sẽ thấy các chức năng bổ sung, chẳng hạn như cột đầu ra cho biết vị trí thứ tự của mỗi phần tử, khả năng lọc ra các chuỗi trùng lặp và chuỗi trống cũng như dấu phân cách nhiều ký tự.

Tôi giải quyết nhiều nhận xét bên dưới trong hai bài đăng tiếp theo:

  • STRING_SPLIT () trong SQL Server 2016:Tiếp theo # 1
  • STRING_SPLIT () trong SQL Server 2016:Tiếp theo # 2

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Trực quan hóa dữ liệu bằng Apache Zeppelin - Hướng dẫn

  2. Làm việc với Salesforce.com trong Alpha Anywhere

  3. Làm thế nào để sử dụng câu lệnh bảng thay thế trong SQL?

  4. Giới thiệu về Thống kê Chờ

  5. Cách khôi phục cơ sở dữ liệu bằng trình quản lý sao lưu