Máy trạng thái hữu hạn trong SQL


7

Tôi muốn một số đầu vào về một vấn đề tôi đang gặp phải. Chúng tôi có một phần mã mà chúng tôi lặp lại trong suốt các quy trình được lưu trữ của mình và mỗi lần mất khá nhiều thời gian để xử lý và khi kết hợp, số lần đọc chạy vào hàng trăm triệu trên một bộ hàng trăm nghìn Mục. Về cơ bản chúng ta có Vật phẩm và Vật phẩm có thể có tới 12 máy, mỗi máy có trạng thái riêng.

Đây là các cấu trúc bảng (đơn giản hóa):

CREATE TABLE dbo.ItemMachineState
(
    [itemID] [int],
    [machineID] [int],
    [stateID] [int]
)

CREATE TABLE dbo.Transition
(
    [machineID] [int] NOT NULL,
    [eventID] [int] NOT NULL,
    [stateID] [int] NOT NULL,
    [nextStateID] [int] NOT NULL
)

Điều gì xảy ra là, trong quá trình xử lý, chúng ta tạo một bảng #temp mà chúng ta làm việc và cuối cùng nó có một eventID cho mỗi Mục. Bảng tạm thời đó sau đó được nối lại vào ItemState và Transition, như vậy:

UPDATE  dbo.ItemState
SET     stateID = tr.nextStateID
FROM    #temp t  
JOIN    dbo.ItemMachineState ist ON ist.itemID = t.itemID
JOIN    Transition tr ON tr.stateID = ist.stateID AND
                         tr.machineID = ist.machineID AND
                         tr.eventID = t.eventID

Vì vậy, eventID, mà chúng tôi tính toán, sẽ xác định điều gì sẽ xảy ra với Máy của một vật phẩm nhất định, tùy thuộc vào trạng thái của chúng. Mỗi vấn đề là một sự kiện có thể điều khiển 0 hoặc nhiều trạng thái máy trong một chuyển động, nếu sự kiện đó có liên quan để kết hợp cụ thể của nhà nước và máy.

Đây là một ví dụ về một trong những thay đổi trạng thái này:

ItemID 3468361 đầu tiên trông như thế này trong ItemMachineState ...

itemID      machineID   stateID
----------- ----------- -----------
3468489     12          4
3468489     14          113
3468489     15          157
3468489     16          165
3468489     18          169
3468489     19          165
3468489     20          157
3468489     21          165
3468489     23          173
3468489     24          173
3468489     26          9
3468489     36          9

Chúng tôi thực hiện một số công việc và cuối cùng có bảng #temp có ItemID và EventID ...

itemID      eventID
----------- -----------
3468489     64

Sau đó, chúng tôi tham gia cả hai bảng này vào Transition, có vẻ như thế này cho sự kiện cụ thể nàyID:

machineID   eventID     stateID     nextStateID
----------- ----------- ----------- -----------
13          64          73          79
13          64          74          79
13          64          75          79
13          64          76          79
13          64          77          79
13          64          78          79
13          64          187         79
13          64          188         79
13          64          189         79
13          64          190         79
13          64          191         79
36          64          9           79
36          64          194         79
36          64          196         79
36          64          208         79
36          64          210         79
36          64          213         79
36          64          218         79
46          64          73          79
47          64          73          79
70          64          73          79
70          64          75          79
70          64          76          79
70          64          77          79
70          64          78          79

Để tất cả chúng cùng nhau:

SELECT  t.itemID, t.eventID, ist.machineID, ist.stateID, tr.nextStateID
FROM    #temp t  
JOIN    dbo.ItemMachineState ist ON ist.itemID = t.itemID
JOIN    Transition tr ON tr.stateID = ist.stateID AND
                         tr.machineID = ist.machineID AND
                         tr.eventID = t.eventID

itemID      eventID     machineID   stateID     nextStateID
----------- ----------- ----------- ----------- -----------
3468489     64          36          9           79

Vì vậy, trong ví dụ cụ thể này, Sự kiện này chỉ liên quan đến một Máy cho Mục này. Đó là stateID được cập nhật từ 9 đến 79 trên machineID 36 và mọi thứ khác vẫn giữ nguyên cho Mục này.

Tôi muốn đề xuất về cách tiếp cận khác nhau này. Chúng ta không thể di chuyển khỏi cấu trúc bảng, nhưng chúng ta có thể thay đổi cách chúng ta sắp đặt trạng tháiID thành nextStateID trong quá trình chuyển đổi / sự kiện. Như bạn có thể thấy ở trên điều này hoạt động bằng cách loại bỏ; chúng ta cần trạng thái hiện tại và máy để tìm ra trạng thái tiếp theo là gì, cho máy đó, cho sự kiện đó. Trong một số trường hợp, điều này sẽ không cập nhật bất cứ điều gì, lần khác nó sẽ cập nhật nhiều máy trong một lần và chúng tôi thích khả năng đó. Tôi không nghĩ rằng giải pháp gọn gàng nhất cho vấn đề này sẽ được tìm thấy bằng cách thay đổi chỉ mục hoặc thêm gợi ý truy vấn và chúng tôi cần một cách tiếp cận mới giới hạn số lần đọc và thời gian xử lý, nhưng cung cấp cho chúng tôi cùng chức năng.


Tôi muốn tránh đưa các chỉ mục và như vậy vào cuộc thảo luận này vì sau đó tôi phải sử dụng các ví dụ thực tế, gây ô nhiễm bản chất của những gì tôi đang cố gắng hỏi ở đây, tôi đã thay đổi tên của các cột và bảng để đơn giản hóa câu hỏi của mình. Trong mọi trường hợp, ở đây bạn đi:

Gói truy vấn http://pastebin.com/xhPa4t8d , tạo và lập chỉ mục các tập lệnh http://pastebin.com/sp70QuEJ

Lưu ý rằng trong kế hoạch truy vấn, chúng tôi buộc INNER LOOP THAM GIA. Nếu để lại một THAM GIA đơn giản, truy vấn sẽ mất nhiều thời gian hơn để xử lý.


Sử dụng chỉ mục @wBob UNIQUE CLUSTERED, trước: Trước

và sau: Sau

Sử dụng OPTION (MERGE JOIN, HASH JOIN)kết quả này kế hoạch và kết quả thực hiện:

Sau khi lựa chọn

Sẽ cập nhật với thông tin khác trong thời gian ngắn


3
Bạn có thể cập nhật câu hỏi của bạn với dbo.ItemStateddl và một số dữ liệu. bạn có thể sử dụng sqlfiddle.com để thiết lập repro. Ngoài ra, có bất kỳ chỉ mục, FK, kích hoạt nào được xác định trên các bảng đó không? Bạn có thể đăng một kế hoạch thực hiện xml thực tế (sử dụng pastebin và liên kết nó ở đây). Điều đó sẽ giúp bạn có được câu trả lời tốt hơn và nhanh hơn :-)
Kin Shah

Từ kế hoạch truy vấn, các hàng Ước tính của bạn (4292220) sẽ tắt hơn Hàng thực tế (747348). Hãy chắc chắn rằng số liệu thống kê của bạn được cập nhật. Ngoài ra, thay vì buộc tham gia FROM #Event AS e INNER LOOP JOIN EFT.AssetState AS ast, bạn có thể sử dụng gợi ý truy vấn OPTION (LOOP JOIN, QUERYTRACEON 8649, QUERYTRACEON 4199);. Tổng bộ nhớ trên máy chủ và bộ nhớ tối đa cũng như cài đặt Max dop là bao nhiêu? Ngoài ra, bạn có thể kiểm tra điều kiện cụ thể bằng cách sử dụng if exists (select .. some criteria = true) then update else do nothingcùng với cập nhật hàng loạt.
Kin Shah

Tại sao bạn sử dụng OPTION (MERGE JOIN, HASH JOIN)cùng một lúc? Bạn đã thử OPTION (LOOP JOIN, QUERYTRACEON 8649, QUERYTRACEON 4199);chưa
Kin Shah

@Kin điều này về cơ bản cho phép Trình tối ưu hóa chọn một trong hai loại tham gia, sắp xếp lại các bảng theo bất kỳ thứ tự nào nó thấy phù hợp, nhưng không sử dụng Vòng lặp lồng nhau. Điều này sẽ giúp loại trừ nếu các vòng lặp Nested là một vấn đề ở đây.
wBob

Câu trả lời:


1

Tôi sẽ xem xét không cập nhật tất cả các hàng cùng một lúc và thay vào đó chạy qua các lô máy để khối lượng hồ sơ giảm trên mỗi lần cập nhật. Bạn có thể giữ cùng một mã, chỉ cần bó nó.


1

Tôi nhận được xung quanh một sự cải thiện hiệu suất 50% trong giàn khoan thử nghiệm của tôi bằng cách tạo ra một bảng temp thứ hai với một nhóm chỉ số duy nhất trên assetIDeventIDvà thả các LOOPgợi ý. Điều này không nên thay đổi về mặt ngữ nghĩa kết quả truy vấn của bạn. Thử cái này:

SELECT DISTINCT assetID, eventID
INTO #Event2
FROM #Event

CREATE UNIQUE CLUSTERED INDEX PK_temp_Event2 ON #Event2 ( assetID, eventID )

UPDATE ast
SET ast.stateID = st.nextStateID
FROM #Event2 AS e
    INNER JOIN EFT.AssetState AS ast
        ON ast.assetID = e.assetID
    INNER JOIN dbo.Transition AS st
        ON st.stateID = ast.stateID
            AND st.eventID = e.eventID
            AND st.machineID = ast.machineID

Hãy cho tôi biết làm thế nào bạn nhận được trên. Nếu nó hoạt động, hãy xem xét điều chỉnh bảng #Event ban đầu của bạn - không có nhu cầu thực sự cho hai bảng tạm thời, điều này chỉ dành cho sự hoàn hảo. điều chỉnh bài tập.

Nếu nó không hoạt động, chúng tôi có thể xem xét cải thiện giàn thử để phản ánh chính xác hơn thiết lập của bạn. Tôi đã thực hiện một số thử nghiệm với ít hoặc không có chỉ mục không phân cụm và nhận được một số kết quả tốt mặc dù rõ ràng các truy vấn khác có thể sử dụng chúng.

Kiểm tra giàn khoan

-- Secondary DDL provided;
USE tempdb
GO

IF NOT EXISTS ( SELECT * FROM sys.schemas WHERE name = 'EFT' )
    EXEC ('CREATE SCHEMA EFT')
GO

IF OBJECT_ID('[dbo].[Transition]') IS NOT NULL DROP TABLE [dbo].[Transition]
IF OBJECT_ID('[EFT].[AssetState]') IS NOT NULL DROP TABLE [EFT].[AssetState]
IF OBJECT_ID('[dbo].[Event]') IS NOT NULL DROP TABLE [dbo].[Event]
IF OBJECT_ID('[dbo].[State]') IS NOT NULL DROP TABLE [dbo].[State]
IF OBJECT_ID('[dbo].[Machine]') IS NOT NULL DROP TABLE [dbo].[Machine]
IF OBJECT_ID('#Event') IS NOT NULL DROP TABLE #Event
GO



-- #EFT.AssetState
CREATE TABLE [EFT].[AssetState](
    [assetID] [int] NOT NULL,
    [busDate] [datetime] NOT NULL,
    [machineID] [int] NOT NULL,
    [stateID] [int] NOT NULL,
 CONSTRAINT [PK_AssetState] PRIMARY KEY CLUSTERED 
(
    [assetID] ASC,
    [busDate] ASC,
    [machineID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_AssetState_assetID] ON [EFT].[AssetState]
(
    [assetID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_AssetState_assetID_stateID] ON [EFT].[AssetState]
(
    [assetID] ASC,
    [stateID] ASC,
    [machineID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_AssetState_machineID_stateID_assetID] ON [EFT].[AssetState]
(
    [machineID] ASC,
    [stateID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO


-- dbo.Transition
CREATE TABLE [dbo].[Transition](
    [transitionID] [int] IDENTITY(1,1) NOT NULL,
    [machineID] [int] NOT NULL,
    [category] [varchar](50) NOT NULL,
    [eventID] [int] NOT NULL,
    [stateID] [int] NOT NULL,
    [nextStateID] [int] NOT NULL,
 CONSTRAINT [PK_Transition] PRIMARY KEY CLUSTERED 
(
    [transitionID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
 CONSTRAINT [UK_Transition_machineID_stateID_eventID] UNIQUE NONCLUSTERED 
(
    [machineID] ASC,
    [stateID] ASC,
    [eventID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [UK_Transition_machineID_nextStateID_eventID] ON [dbo].[Transition]
(
    [machineID] ASC,
    [eventID] ASC,
    [stateID] ASC,
    [nextStateID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO



CREATE TABLE [dbo].[State](
    [stateID]   INT PRIMARY KEY
    )
GO

CREATE TABLE [dbo].[Event](
    [eventID]   INT PRIMARY KEY
    )
GO

CREATE TABLE [dbo].[Machine](
    [machineID] INT PRIMARY KEY
    )
GO



ALTER TABLE [dbo].[Transition]  WITH CHECK ADD  CONSTRAINT [FK_Transition_NextState] FOREIGN KEY([nextStateID])
REFERENCES [dbo].[State] ([stateID])
GO

ALTER TABLE [dbo].[Transition] CHECK CONSTRAINT [FK_Transition_NextState]
GO

ALTER TABLE [dbo].[Transition]  WITH CHECK ADD  CONSTRAINT [FK_Transition_State] FOREIGN KEY([stateID])
REFERENCES [dbo].[State] ([stateID])
GO

ALTER TABLE [dbo].[Transition] CHECK CONSTRAINT [FK_Transition_State]
GO

ALTER TABLE [dbo].[Transition]  WITH CHECK ADD  CONSTRAINT [FK_Transition_StateEvent] FOREIGN KEY([eventID])
REFERENCES [dbo].[Event] ([eventID])
GO

ALTER TABLE [dbo].[Transition] CHECK CONSTRAINT [FK_Transition_StateEvent]
GO

ALTER TABLE [dbo].[Transition]  WITH CHECK ADD  CONSTRAINT [FK_Transition_StateMachine] FOREIGN KEY([machineID])
REFERENCES [dbo].[Machine] ([machineID])
GO

ALTER TABLE [dbo].[Transition] CHECK CONSTRAINT [FK_Transition_StateMachine]
GO

-- #Event
CREATE TABLE #Event
(
    assetID INT     ,
    busDate DATETIME,
    eventID INT     
)

CREATE CLUSTERED INDEX IX_Ev_assetID ON #Event ( assetID )
GO



-- Create dummy data
-- populate AssetState with 2,658,200 records
--  2,658,200
;WITH cte AS (
SELECT TOP 1000000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
FROM master.sys.columns c1
    CROSS JOIN master.sys.columns c2
    CROSS JOIN master.sys.columns c3
)
INSERT INTO [EFT].[AssetState]( assetID, busDate, machineID, stateID )
SELECT 
    items.rn AS assetID,
    '1 Jan 2015' AS busDate,
    machines.rn AS machineID,
    items.rn % 7 AS stateID
FROM
    ( SELECT TOP 221520 * FROM cte ) items
    CROSS JOIN
    ( SELECT TOP (12) * FROM cte ) machines
GO


-- Get a random selection for temp table
INSERT INTO #Event ( assetID, busDate, eventID )
SELECT TOP (2128660) assets.assetID, assets.busDate, assets.assetID % 99 AS eventID
FROM ( SELECT DISTINCT assetID, busDate FROM [EFT].[AssetState] ) assets
    CROSS JOIN
    ( SELECT TOP (12) * FROM [EFT].[AssetState] ) machines
ORDER BY NEWID()
GO


-- Get selection for Transition table
INSERT INTO [dbo].[State] ( stateID )
SELECT assetID
FROM ( SELECT DISTINCT TOP 99 assetID FROM [EFT].[AssetState] ) m
GO

INSERT INTO [dbo].[Event] ( eventID )
SELECT assetID
FROM ( SELECT DISTINCT TOP 99 assetID FROM [EFT].[AssetState] ) m
GO

INSERT INTO [dbo].[Machine] ( machineID )
SELECT machineID
FROM ( SELECT DISTINCT machineID FROM [EFT].[AssetState] ) m
GO




INSERT INTO dbo.Transition ( machineID, category, eventID, stateID, nextStateID )
SELECT TOP (1214)
    m.machineID,
    CASE x.rn % 3 WHEN 0 THEN 'X' WHEN 1 THEN 'Y' WHEN 2 THEN 'Z' END category, 
    ( x.rn % 99 ) + 1 eventID,
    ( x.rn % 7 ) + 1 stateID,
    ( x.rn % 7 ) + 2 nextStateID
FROM ( SELECT DISTINCT machineID FROM [EFT].[AssetState] ) m
    CROSS JOIN
    ( SELECT TOP (102) ROW_NUMBER() OVER( ORDER BY ( SELECT NULL ) ) rn, * FROM [EFT].[AssetState] ) x
ORDER BY NEWID()
GO
--:exit



-- Original
DECLARE @startDate DATETIME = GETDATE()
BEGIN TRAN

UPDATE  EFT.AssetState
SET stateID = st.nextStateID
FROM    #Event AS e
    INNER LOOP JOIN
        EFT.AssetState AS ast
    ON ast.assetID = e.assetID
        INNER JOIN
        Transition AS st
    ON st.stateID = ast.stateID
        AND st.eventID = e.eventID
        AND st.machineID = ast.machineID;

SELECT @@rowcount r, DATEDIFF( s, @startDate, GETDATE() ) diff1

ROLLBACK TRAN
GO



-- Revised
DECLARE @startDate DATETIME = GETDATE()

IF OBJECT_ID('tempdb..#Event2') IS NOT NULL DROP TABLE #Event2

SELECT DISTINCT assetID, eventID
INTO #Event2
FROM #Event

CREATE UNIQUE CLUSTERED INDEX PK_temp_Event2 ON #Event2 ( assetID, eventID )

BEGIN TRAN

UPDATE ast
SET ast.stateID = st.nextStateID
FROM #Event2 AS e
    INNER JOIN EFT.AssetState AS ast
        ON ast.assetID = e.assetID
    INNER JOIN dbo.Transition AS st
        ON st.stateID = ast.stateID
            AND st.eventID = e.eventID
            AND st.machineID = ast.machineID

SELECT @@rowcount r, DATEDIFF( s, @startDate, GETDATE() ) diff2

ROLLBACK TRAN
GO

Cập nhật 1: Bạn có nghĩa là 22 triệu hồ sơ không đọc? Bạn không có bất kỳ WHEREmệnh đề nào nên bạn sẽ được quét. Bạn có thể nhận được các tìm kiếm cho bảng bên ngoài của phép nối Nested Loops, nhưng bảng nhỏ hơn sẽ nằm ở trên cùng. Tôi sẽ bị cám dỗ để cố gắng OPTION ( MERGE JOIN, HASH JOIN )loại bỏ các vòng lặp lồng nhau ở đây để xem cách bạn tiếp tục. Phương pháp này cũng có thêm lợi ích của việc không thực thi lệnh tham gia. Tôi đề nghị điều này để thu thập thông tin không nhất thiết phải là một sửa chữa cho sản xuất. Bất kỳ đề xuất về cải thiện giàn thử để phản ánh chính xác hơn thiết lập của bạn?

Mất bao lâu để hai truy vấn? Bạn có thể thử chạy chúng thông qua một cái gì đó như Plan Explorer (phiên bản miễn phí) vì tôi nghĩ rằng nó sẽ được chiếu sáng. Tôi nhận thấy có một lỗi trong thiết bị thử nghiệm của tôi khi tôi bao gồm thời gian tạo chỉ mục trong thời gian cho giàn thứ hai, vì vậy vui lòng loại trừ. Đối với kết quả của tôi, tôi nhận được truy vấn ban đầu 15 giây và truy vấn sửa đổi 7 giây:

Kiểm tra kết quả giàn khoan

Cập nhật 2: Đã làm việc với OP để xóa các chỉ mục không được nhóm, xóa gợi ý nối vòng lặp và thêm chỉ mục duy nhất vào bảng tạm thời để được cải thiện 75%. Đầu vào tuyệt vời và tín dụng cho @PaulWhite.


Theo yêu cầu, một phòng trò chuyện chuyên dụng đã được tạo để thảo luận xung quanh câu trả lời này.
Paul White 9
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.