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

Các TVF đa câu lệnh trong Dynamics CRM

Tác giả:Andy Mallon (@AMtwo)

Nếu bạn đã quen với việc hỗ trợ cơ sở dữ liệu đằng sau Microsoft Dynamics CRM, bạn có thể biết rằng nó không phải là cơ sở dữ liệu hoạt động nhanh nhất. Thành thật mà nói, đó không phải là một điều ngạc nhiên - nó không được thiết kế để trở thành một cơ sở dữ liệu nhanh. Nó được thiết kế để trở thành một linh hoạt cơ sở dữ liệu. Hầu hết các hệ thống Quản lý Quan hệ Khách hàng (CRM) được thiết kế linh hoạt để có thể đáp ứng nhu cầu của nhiều doanh nghiệp trong nhiều ngành với các yêu cầu kinh doanh khác nhau. Họ đặt những yêu cầu đó lên trước hiệu suất cơ sở dữ liệu. Đó có lẽ là cách kinh doanh thông minh, nhưng tôi không phải là người kinh doanh - tôi là người làm cơ sở dữ liệu. Trải nghiệm của tôi với Dynamics CRM là khi mọi người đến gặp tôi và nói

Andy, cơ sở dữ liệu chậm

Một lần xảy ra gần đây là báo cáo không thành công do hết thời gian chờ truy vấn 5 phút. Với các chỉ mục thích hợp, chúng tôi sẽ có thể nhận được vài trăm hàng thực sự nhanh chóng . Tôi đã chạm tay vào truy vấn và một số tham số ví dụ, thả nó vào Plan Explorer và chạy nó một vài lần trong môi trường Thử nghiệm của chúng tôi (Tôi đang thực hiện tất cả những điều này trong Thử nghiệm – điều đó sẽ quan trọng sau này). Tôi muốn đảm bảo rằng tôi đang chạy nó với một bộ nhớ đệm ấm, để tôi có thể sử dụng "tốt nhất trong số những thứ tồi tệ nhất" cho điểm chuẩn của mình. Truy vấn là một SELECT rất khó chịu với một CTE và một loạt các liên kết. Rất tiếc, tôi không thể cung cấp truy vấn chính xác vì nó có một số logic nghiệp vụ dành riêng cho khách hàng (Xin lỗi!).

7 phút, 37 giây là tốt nhất có thể.

Ngay lập tức, có rất nhiều điều tồi tệ đang xảy ra ở đây. 1,5 triệu lượt đọc là một con số khổng lồ của rất nhiều I / O. 457 giây để trả về 200 hàng là chậm. Công cụ ước tính số lượng cần có 2 hàng, thay vì 200. Và có rất nhiều lần ghi – vì truy vấn này chỉ là một SELECT , điều này có nghĩa là chúng ta phải tràn sang TempDb. Có lẽ tôi sẽ gặp may và có thể tạo chỉ mục để loại bỏ việc quét bảng và tăng tốc độ này. Kế hoạch trông như thế nào?

Trông giống như một con apatosaurus hoặc có thể là một con hươu cao cổ.

Sẽ không có lượt truy cập nhanh nào

Hãy để tôi tạm dừng một chút để giải thích đôi điều về Dynamics CRM. Nó sử dụng các khung nhìn. Nó sử dụng các khung nhìn lồng nhau. Nó sử dụng các khung nhìn lồng nhau để thực thi bảo mật cấp hàng. Theo cách nói của Dynamics, các chế độ xem lồng nhau ở cấp độ hàng-thực thi bảo mật này được gọi là "chế độ xem được lọc". Mọi truy vấn từ ứng dụng đều đi qua các chế độ xem đã lọc này. Cách duy nhất "được hỗ trợ" để thực hiện truy cập dữ liệu là sử dụng các chế độ xem đã lọc này.

Nhớ lại tôi đã nói truy vấn này đang tham chiếu đến một loạt các bảng? Chà, nó đang tham chiếu đến một loạt các chế độ xem đã lọc. Vì vậy, truy vấn phức tạp mà tôi đã được giao thực sự phức tạp hơn nhiều lớp. Tại thời điểm này, tôi đã có một tách cà phê mới và chuyển sang màn hình lớn hơn.

Một cách tuyệt vời để giải quyết vấn đề là bắt đầu ngay từ đầu. Tôi đã phóng to toán tử SELECT và làm theo các mũi tên để xem điều gì đang xảy ra:

Ngay cả trên màn hình siêu rộng 34 ", tôi vẫn phải loay hoay với màn hình cài đặt cho kế hoạch để xem nhiều điều này. Plan Explorer có thể xoay kế hoạch 90 độ để làm cho kế hoạch "cao" vừa với màn hình rộng.

Hãy xem tất cả các lệnh gọi hàm có giá trị bảng đó! Tiếp theo ngay lập tức là một trận đấu băm thực sự đắt tiền. Giác quan Spidey của tôi bắt đầu râm ran. fn_GetMaxPrivilegeDepthMask là gì , và tại sao nó được gọi là 30 lần? Tôi cá rằng đây là một vấn đề. Khi bạn thấy "Hàm có giá trị bảng" dưới dạng toán tử trong một kế hoạch, điều đó thực sự có nghĩa đó là một hàm có giá trị bảng trong nhiều câu lệnh . Nếu đó là một hàm có giá trị trong bảng nội tuyến, nó sẽ được kết hợp vào kế hoạch lớn hơn, và không phải là một hộp đen. Các hàm có giá trị trong bảng nhiều câu lệnh là xấu. Đừng sử dụng chúng. Công cụ Ước tính Cardinality không thể đưa ra các ước tính chính xác. Trình tối ưu hoá Truy vấn không thể tối ưu hoá chúng trong ngữ cảnh của truy vấn lớn hơn. Từ góc độ hiệu suất, chúng không mở rộng quy mô.

Mặc dù TVF này là một đoạn mã có sẵn từ Dynamics CRM, Spidey Sense của tôi cho tôi biết rằng đó là vấn đề. Hãy quên đi truy vấn khó chịu lớn này bằng một kế hoạch đáng sợ lớn. Hãy bước vào chức năng đó và xem điều gì đang xảy ra:

create function [dbo].[fn_GetMaxPrivilegeDepthMask](@ObjectTypeCode int) 
returns @d table(PrivilegeDepthMask int)
-- It is by design that we return a table with only one row and column
as
begin
	declare @UserId uniqueidentifier
	select @UserId = dbo.fn_FindUserGuid()
 
	declare @t table(depth int)
 
	-- from user roles
	insert into @t(depth)	
	select
	--privilege depth mask = 1(basic) 2(local) 4(deep) and 8(global) 
	-- 16(inherited read) 32(inherited local) 64(inherited deep) and 128(inherited global)
	-- do an AND with 0x0F ( =15) to get basic/local/deep/global
		max(rp.PrivilegeDepthMask % 0x0F)
	   as PrivilegeDepthMask
	from 
		PrivilegeBase priv
		join RolePrivileges rp on (rp.PrivilegeId = priv.PrivilegeId)
		join Role r on (rp.RoleId = r.ParentRootRoleId)
		join SystemUserRoles ur on (r.RoleId = ur.RoleId and ur.SystemUserId = @UserId)
		join PrivilegeObjectTypeCodes potc on (potc.PrivilegeId = priv.PrivilegeId)
	where 
		potc.ObjectTypeCode = @ObjectTypeCode and 
		priv.AccessRight & 0x01 = 1
 
	-- from user's teams roles
	insert into @t(depth)	
	select
	--privilege depth mask = 1(basic) 2(local) 4(deep) and 8(global) 
	-- 16(inherited read) 32(inherited local) 64(inherited deep) and 128(inherited global)
	-- do an AND with 0x0F ( =15) to get basic/local/deep/global
		max(rp.PrivilegeDepthMask % 0x0F)
	   as PrivilegeDepthMask
	from 
		PrivilegeBase priv
        join RolePrivileges rp on (rp.PrivilegeId = priv.PrivilegeId)
        join Role r on (rp.RoleId = r.ParentRootRoleId)
        join TeamRoles tr on (r.RoleId = tr.RoleId)
        join SystemUserPrincipals sup on (sup.PrincipalId = tr.TeamId and sup.SystemUserId = @UserId)
        join PrivilegeObjectTypeCodes potc on (potc.PrivilegeId = priv.PrivilegeId)
	where 
		potc.ObjectTypeCode = @ObjectTypeCode and 
		priv.AccessRight & 0x01 = 1
 
	insert into @d select max(depth) from @t
	return	
end		
GO

Hàm này tuân theo một mẫu cổ điển trong TVF nhiều câu lệnh:

  • Khai báo một biến được sử dụng như một hằng số
  • Chèn vào một biến bảng
  • Trả về biến bảng đó

Không có gì lạ mắt xảy ra ở đây. Chúng tôi có thể viết lại nhiều câu lệnh này thành một SELECT duy nhất tuyên bố. Nếu chúng ta có thể viết nó dưới dạng một SELECT duy nhất , chúng tôi có thể viết lại điều này dưới dạng TVF nội tuyến.

Hãy làm điều đó

Nếu nó không rõ ràng, tôi sẽ viết lại mã do một nhà cung cấp phần mềm cung cấp. Tôi chưa bao giờ gặp một nhà cung cấp phần mềm coi đây là hành vi "được hỗ trợ". Nếu bạn thay đổi mã ứng dụng xuất xưởng, bạn đang ở trong chính mình. Microsoft chắc chắn coi đây là hành vi "không được hỗ trợ" cho Dynamics. Dù sao thì tôi cũng sẽ làm điều đó, vì tôi đang sử dụng môi trường thử nghiệm và tôi không tham gia vào quá trình sản xuất. Việc viết lại hàm này chỉ mất vài phút - vậy tại sao bạn không thử và xem điều gì sẽ xảy ra? Đây là phiên bản hàm của tôi trông như thế nào:

create function [dbo].[fn_GetMaxPrivilegeDepthMask](@ObjectTypeCode int) 
returns table
-- It is by design that we return a table with only one row and column
as
RETURN
	-- from user roles
	select PrivilegeDepthMask = max(PrivilegeDepthMask) 
	    from	(
	    select
            --privilege depth mask = 1(basic) 2(local) 4(deep) and 8(global) 
	    -- 16(inherited read) 32(inherited local) 64(inherited deep) and 128(inherited global)
	    -- do an AND with 0x0F ( =15) to get basic/local/deep/global
		    max(rp.PrivilegeDepthMask % 0x0F)
	       as PrivilegeDepthMask
	    from 
		    PrivilegeBase priv
		    join RolePrivileges rp on (rp.PrivilegeId = priv.PrivilegeId)
		    join Role r on (rp.RoleId = r.ParentRootRoleId)
		    join SystemUserRoles ur on (r.RoleId = ur.RoleId and ur.SystemUserId = dbo.fn_FindUserGuid())
		    join PrivilegeObjectTypeCodes potc on (potc.PrivilegeId = priv.PrivilegeId)
	    where 
		    potc.ObjectTypeCode = @ObjectTypeCode and 
		    priv.AccessRight & 0x01 = 1
        UNION ALL	
	    -- from user's teams roles
	    select
            --privilege depth mask = 1(basic) 2(local) 4(deep) and 8(global) 
	    -- 16(inherited read) 32(inherited local) 64(inherited deep) and 128(inherited global)
	    -- do an AND with 0x0F ( =15) to get basic/local/deep/global
		    max(rp.PrivilegeDepthMask % 0x0F)
	       as PrivilegeDepthMask
	    from 
		    PrivilegeBase priv
            join RolePrivileges rp on (rp.PrivilegeId = priv.PrivilegeId)
            join Role r on (rp.RoleId = r.ParentRootRoleId)
            join TeamRoles tr on (r.RoleId = tr.RoleId)
            join SystemUserPrincipals sup on (sup.PrincipalId = tr.TeamId and sup.SystemUserId = dbo.fn_FindUserGuid())
            join PrivilegeObjectTypeCodes potc on (potc.PrivilegeId = priv.PrivilegeId)
	    where 
		    potc.ObjectTypeCode = @ObjectTypeCode and 
		    priv.AccessRight & 0x01 = 1
        )x
GO

Tôi quay lại truy vấn thử nghiệm ban đầu của mình, giải phóng bộ nhớ cache và chạy lại một vài lần. Đây là chậm nhất thời gian chạy khi sử dụng phiên bản TVF của tôi:

Trông đẹp hơn nhiều!

Nó vẫn chưa phải là truy vấn hiệu quả nhất trên thế giới, nhưng nó đủ nhanh - tôi không cần phải làm nó nhanh hơn nữa. Ngoại trừ… tôi đã phải sửa đổi mã của Microsoft để làm cho nó xảy ra. Đó không phải là lý tưởng. Hãy cùng xem toàn bộ kế hoạch với TVF mới:

Tạm biệt apatosaurus, xin chào máy rút PEZ!

Đó vẫn là một kế hoạch thực sự nguy hiểm, nhưng nếu bạn nhìn vào đầu, tất cả các cuộc gọi TVF hộp đen đó đã biến mất. Trận đấu băm siêu đắt đã biến mất. SQL Server có thể hoạt động ngay mà không bị tắc nghẽn lớn khi gọi TVF (công việc đằng sau TVF hiện đã phù hợp với phần còn lại của SELECT ):

Ảnh hưởng lớn đến hình ảnh

TVF này thực sự được sử dụng ở đâu? Gần như mọi chế độ xem được lọc trong Dynamics CRM đều sử dụng lệnh gọi hàm này. Có 246 chế độ xem được lọc và 206 trong số chúng tham chiếu đến chức năng này. Đây là một chức năng quan trọng như là một phần của quá trình triển khai bảo mật cấp hàng Dynamics. Hầu như mọi truy vấn đơn lẻ từ ứng dụng đến cơ sở dữ liệu đều gọi hàm này ít nhất một lần – thường là một vài lần. Đây là đồng tiền có hai mặt:một mặt, việc sửa chữa chức năng này có thể sẽ hoạt động như một động cơ tăng áp cho toàn bộ ứng dụng; mặt khác, không có cách nào để tôi thực hiện kiểm tra hồi quy cho mọi thứ liên quan đến chức năng này.

Chờ một chút – nếu lệnh gọi hàm này rất quan trọng đối với hiệu suất của chúng tôi và cũng là cốt lõi đối với Dynamics CRM, thì tất cả những người sử dụng Dynamics đều gặp phải nút thắt hiệu suất này. Chúng tôi đã mở một trường hợp với Microsoft và tôi đã gọi cho một vài người để yêu cầu nhóm kỹ thuật chịu trách nhiệm về mã này. Với một chút may mắn, phiên bản cập nhật này của chức năng sẽ được đưa vào hộp (và đám mây) trong bản phát hành Dynamics CRM trong tương lai.

Đây không phải là TVF nhiều câu lệnh duy nhất trong Dynamics CRM – Tôi đã thực hiện cùng một kiểu thay đổi đối với fn_UserSharedAttributesAccess cho một vấn đề hiệu suất khác. Và còn nhiều TVF nữa mà tôi chưa đụng đến vì chúng chưa gây ra sự cố.

Một bài học cho tất cả mọi người, ngay cả khi bạn không sử dụng Dynamics

Nhắc lại sau tôi: CÁC CHỨC NĂNG CÓ GIÁ TRỊ CỦA BẢNG ĐA SỐ ĐƯỢC ĐÁNH GIÁ!

Hệ số lại mã của bạn để tránh sử dụng TVF nhiều câu lệnh. Nếu bạn đang cố gắng điều chỉnh mã và bạn thấy TVF có nhiều câu lệnh, hãy xem xét nó một cách nghiêm túc. Không phải lúc nào bạn cũng có thể thay đổi mã (hoặc có thể vi phạm hợp đồng hỗ trợ của bạn nếu bạn làm vậy), nhưng nếu bạn có thể thay đổi mã, hãy làm điều đó. Yêu cầu nhà cung cấp phần mềm của bạn ngừng sử dụng TVF nhiều câu lệnh. Làm cho thế giới trở thành một nơi tốt đẹp hơn bằng cách loại bỏ một số chức năng khó chịu này khỏi cơ sở dữ liệu của bạn.

Giới thiệu về tác giả

Andy Mallon là một SQL Server DBA và Microsoft Data Platform MVP đã quản lý cơ sở dữ liệu trong lĩnh vực chăm sóc sức khỏe, tài chính, đ -các lĩnh vực thương mại và phi lợi nhuận. Kể từ năm 2003, Andy đã hỗ trợ các môi trường OLTP dung lượng lớn, có tính khả dụng cao với các nhu cầu về hiệu suất khắt khe. Andy là người sáng lập BostonSQL, đồng tổ chức SQLSaturday Boston, và viết blog tại am2.co.
  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Các blog cơ sở dữ liệu hàng đầu để theo dõi

  2. Kết quả không chính xác với kết hợp hợp nhất

  3. Chứng chỉ Chuyên gia Google Data Analytics có giá trị không?

  4. Kết nối Talend trên Windows với Cơ sở dữ liệu ODBC

  5. Quản trị bảo mật dữ liệu