Làm thế nào để có được phản hồi từ thủ tục được lưu trữ trước khi nó kết thúc?


8

Tôi cần trả về kết quả một phần (như chọn đơn giản) từ thủ tục Lưu trước khi kết thúc.

Có thể làm điều đó?

Nếu có, làm thế nào để làm điều đó?

Nếu không, cách giải quyết nào?

EDIT: Tôi có một số phần của thủ tục. Trong phần đầu tiên tôi tính một số chuỗi. Tôi sử dụng chúng sau này trong quy trình để thực hiện các hoạt động gây nghiện. Vấn đề là chuỗi đó được người gọi cần càng sớm càng tốt. Vì vậy, tôi cần tính toán chuỗi đó và chuyển nó trở lại (bằng cách nào đó, từ một lựa chọn chẳng hạn) và sau đó tiếp tục làm việc. Người gọi nhận được chuỗi giá trị của nó nhanh hơn nhiều.

Người gọi là một dịch vụ web.


Giả sử khóa bảng hoàn chỉnh chưa xảy ra hoặc giao dịch rõ ràng chưa được khai báo, bạn sẽ có thể chạy CHỌN trong một phiên riêng mà không gặp sự cố.
Steve Mangiameli 18/03/2016

Nói chung đây là cách duy nhất tôi thấy bây giờ, nhưng tôi không nghĩ nó sẽ nhanh hơn nhiều (cũng có những vấn đề khác), @SteveMangiameli
Bogdan Bogdanov

Chia nó thành hai SP? Vượt qua đầu ra từ thứ nhất đến thứ hai.
paparazzo

Giải pháp không nhanh lắm, đó là lý do tại sao chúng tôi đã loại bỏ nó, @Paparazzi
Bogdan Bogdanov

Câu trả lời:


11

Bạn có thể đang tìm kiếm RAISERRORlệnh với NOWAITtùy chọn.

Theo nhận xét :

RAISERROR có thể được sử dụng thay thế cho IN để trả lại tin nhắn cho các ứng dụng gọi điện.

Điều này không trả về kết quả từ một SELECTcâu lệnh, nhưng nó sẽ cho phép bạn chuyển các tin nhắn / chuỗi trở lại máy khách. Nếu bạn muốn trả về một tập hợp con nhanh chóng của dữ liệu bạn đang chọn thì bạn có thể muốn xem xét FASTgợi ý truy vấn.

Chỉ định rằng truy vấn được tối ưu hóa để truy xuất nhanh số đầu tiên. Đây là một số nguyên không âm. Sau khi số đầu tiên được trả về, truy vấn tiếp tục thực hiện và tạo tập kết quả đầy đủ của nó.

Được thêm bởi Shannon Severance trong một bình luận:

Từ Lỗi và Xử lý giao dịch trong SQL Server của Erland Sommarskog:

Mặc dù vậy, hãy coi chừng rằng một số API và công cụ có thể đệm về phía chúng, do đó vô hiệu hóa tác dụng của WITH NOWAIT.

Xem bài viết nguồn cho bối cảnh đầy đủ.


FASTđã giải quyết vấn đề cho tôi trong một vấn đề mà tôi cần phải đồng bộ hóa việc thực thi một thủ tục được lưu trữ và mã C # để làm trầm trọng thêm và tái tạo một điều kiện cuộc đua. Dễ dàng sử dụng các kết quả theo chương trình hơn là sử dụng một cái gì đó như RAISERROR(). Khi tôi bắt đầu đọc câu trả lời của bạn, có vẻ như bạn đang nói rằng nó không thể được thực hiện SELECT, vì vậy có lẽ điều đó có thể được làm rõ?
b Liệu

5

CẬP NHẬT: Xem câu trả lời của strutzky ( ở trên ) và các bình luận cho ít nhất một ví dụ trong đó điều này không hành xử như tôi mong đợi và mô tả ở đây. Tôi sẽ phải thử nghiệm / đọc thêm để cập nhật sự hiểu biết của mình khi thời gian cho phép ...

Nếu người gọi của bạn tương tác với cơ sở dữ liệu không đồng bộ hoặc được phân luồng / đa tiến trình, do đó bạn có thể mở phiên thứ hai trong khi phiên thứ nhất vẫn đang chạy, bạn có thể tạo một bảng để giữ dữ liệu một phần và cập nhật khi tiến trình. Điều này sau đó có thể được đọc bởi phiên thứ hai với mức cách ly giao dịch 1 được đặt để cho phép nó đọc các thay đổi không được cam kết:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM progress_table

1: theo nhận xét và cập nhật tiếp theo trong câu trả lời của srutzky, không cần thiết lập mức cô lập nếu quy trình được giám sát không được gói trong một giao dịch, mặc dù tôi có xu hướng bỏ thói quen trong những trường hợp như vậy vì nó không gây ra tác hại khi không cần thiết trong những trường hợp này

Tất nhiên, nếu bạn có thể có nhiều quy trình hoạt động theo cách này (có thể là nếu máy chủ web của bạn chấp nhận người dùng đồng thời và rất hiếm khi không xảy ra trường hợp đó), bạn sẽ cần xác định thông tin tiến trình cho quy trình này theo một cách nào đó . Có lẽ vượt qua thủ tục một UUID mới được đúc làm khóa, thêm nó vào bảng tiến trình và đọc với:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM progress_table WHERE process = <current_process_uuid>

Tôi đã sử dụng phương pháp này để theo dõi các quy trình thủ công chạy dài trong SSMS. Tôi không thể quyết định liệu nó có "ngửi" quá nhiều để tôi cân nhắc sử dụng nó trong sản xuất hay không ...


1
Đây là một lựa chọn nhưng tôi không thích nó vào lúc này. Tôi hy vọng một số tùy chọn khác sẽ bật lên.
Bogdan Bogdanov

5

OP đã thử gửi nhiều bộ kết quả (không phải MARS) và thấy rằng nó thực sự chờ Thủ tục lưu trữ hoàn thành trước khi trả lại bất kỳ bộ kết quả nào. Với tình hình đó, đây là một số tùy chọn:

  1. Nếu dữ liệu của bạn đủ nhỏ để vừa trong phạm vi 128 byte, rất có thể bạn có thể sử dụng SET CONTEXT_INFOđể hiển thị giá trị đó qua SELECT [context_info] FROM [sys].[dm_exec_requests] WHERE [session_id] = @SessionID;. Bạn sẽ chỉ cần thực hiện một truy vấn nhanh trước khi bạn chạy Thủ tục được lưu trữ SELECT @@SPID;và lấy thông qua đó SqlCommand.ExecuteScalar.

    Tôi chỉ thử nghiệm điều này và nó hoạt động.

  2. Tương tự như đề xuất của @ David để đưa dữ liệu vào bảng "tiến trình", nhưng không cần phải gây rối với các vấn đề dọn dẹp hoặc đồng thời / phân tách quy trình:

    1. Tạo một cái mới Guidtrong mã ứng dụng và chuyển nó dưới dạng tham số cho Thủ tục lưu trữ. Lưu trữ Hướng dẫn này trong một biến vì nó sẽ được sử dụng nhiều lần.
    2. Trong Quy trình được lưu trữ, tạo Bảng tạm thời toàn cầu bằng cách sử dụng Hướng dẫn đó làm một phần của tên bảng, đại loại như thế CREATE TABLE ##MyProcess_{GuidFromApp};. Bảng có thể có bất kỳ cột nào của bất kỳ kiểu dữ liệu nào bạn cần.
    3. Bất cứ khi nào bạn có dữ liệu, hãy chèn nó vào Bảng tạm thời toàn cầu đó.

    4. Trong mã ứng dụng, bắt đầu cố gắng để đọc dữ liệu, nhưng quấn SELECTtrong một IF EXISTSvì vậy nó sẽ không thất bại nếu bảng chưa được nào được tạo:

      IF (OBJECT_ID('tempdb..[##MyProcess_{0}]')
          IS NOT NULL)
      BEGIN
        SELECT * FROM [##MyProcess_{0}];
      END;
      

    Với String.Format(), bạn có thể thay thế {0}bằng giá trị trong biến Guid. Kiểm tra xem nếu Reader.HasRowsvà nếu đúng thì đọc kết quả, gọi khác Thread.Sleep()hoặc bất cứ điều gì để thăm dò lại.

    Những lợi ích:

    • Bảng này được phân lập từ các quy trình khác vì chỉ có mã ứng dụng biết giá trị Hướng dẫn cụ thể, do đó không cần phải lo lắng về các quy trình khác. Một quá trình khác sẽ có bảng tạm thời toàn cầu riêng của mình.
    • Bởi vì nó là một cái bàn, mọi thứ đều được gõ mạnh.
    • Vì là bảng tạm thời, khi phiên thực hiện Quy trình được lưu trữ kết thúc, bảng sẽ tự động được dọn sạch.
    • Bởi vì nó là một bảng tạm thời toàn cầu :
      • các phiên khác có thể truy cập được, giống như một bảng cố định
      • nó sẽ tồn tại đến cuối của quá trình phụ được tạo ra (tức là EXEC/ sp_executesqlgọi)


    Tôi đã thử nghiệm điều này và nó hoạt động như mong đợi. Bạn có thể tự thử nó với mã ví dụ sau.

    Trong một tab truy vấn, hãy chạy như sau, sau đó tô sáng 3 dòng trong nhận xét khối và chạy:

    CREATE
    --ALTER
    PROCEDURE #GetSomeInfoBackQuickly
    (
      @MessageTableName NVARCHAR(50) -- might not always be a GUID
    )
    AS
    SET NOCOUNT ON;
    
    DECLARE @SQL NVARCHAR(MAX) = N'CREATE TABLE [##MyProcess_' + @MessageTableName
                 + N'] (Message1 NVARCHAR(50), Message2 NVARCHAR(50), SomeNumber INT);';
    
    -- Do some calculations
    
    EXEC (@SQL);
    
    SET @SQL = N'INSERT INTO [##MyProcess_' + @MessageTableName
    + N'] (Message1, Message2, SomeNumber) VALUES (@Msg1, @Msg2, @SomeNum);';
    
    DECLARE @SomeNumber INT = CRYPT_GEN_RANDOM(2);
    
    EXEC sp_executesql
        @SQL,
        N'@Msg1 NVARCHAR(50), @Msg2 NVARCHAR(50), @SomeNum INT',
        @Msg1 = N'wow',
        @Msg2 = N'yadda yadda yadda',
        @SomeNum = @SomeNumber;
    
    WAITFOR DELAY '00:00:10.000';
    
    SET @SomeNumber = CRYPT_GEN_RANDOM(3);
    EXEC sp_executesql
        @SQL,
        N'@Msg1 NVARCHAR(50), @Msg2 NVARCHAR(50), @SomeNum INT',
        @Msg1 = N'wow',
        @Msg2 = N'yadda yadda yadda',
        @SomeNum = @SomeNumber;
    
    WAITFOR DELAY '00:00:10.000';
    GO
    /*
    DECLARE @TempTableID NVARCHAR(50) = NEWID();
    RAISERROR('%s', 10, 1, @TempTableID) WITH NOWAIT;
    
    EXEC #GetSomeInfoBackQuickly @TempTableID;
    */
    

    Chuyển đến tab "Tin nhắn" và sao chép GUID đã được in. Sau đó, mở một tab truy vấn khác và chạy như sau, đặt GUID mà bạn đã sao chép từ tab Tin nhắn của Phiên khác vào khởi tạo biến trên dòng 1:

    DECLARE @TempTableID NVARCHAR(50) = N'GUID-from-other-session';
    
    EXEC (N'SELECT * FROM [##MyProcess_' + @TempTableID + N']');
    

    Tiếp tục đánh F5. Bạn sẽ thấy 1 mục trong 10 giây đầu tiên, và sau đó 2 mục trong 10 giây tiếp theo.

  3. Bạn có thể sử dụng SQLCLR để thực hiện cuộc gọi trở lại ứng dụng của mình thông qua Dịch vụ web hoặc một số phương tiện khác.

  4. Bạn có thể sử dụng PRINT/ RAISERROR(..., 1, 10) WITH NOWAITđể chuyển các chuỗi trở lại ngay lập tức, nhưng điều này sẽ hơi khó khăn do các vấn đề sau:

    • Đầu ra "Tin nhắn" bị giới hạn ở một trong hai VARCHAR(8000)hoặcNVARCHAR(4000)
    • Tin nhắn không được gửi theo cách tương tự như kết quả. Để nắm bắt chúng, bạn cần thiết lập một trình xử lý sự kiện. Trong trường hợp đó, bạn có thể tạo một biến dưới dạng một bộ sưu tập tĩnh để nhận các thông báo có sẵn cho tất cả các phần của mã. Hoặc có thể một số cách khác. Tôi có một hoặc hai ví dụ trong các câu trả lời khác ở đây chỉ ra cách nắm bắt các tin nhắn và sẽ liên kết với chúng sau này khi tôi tìm thấy chúng.
    • Tin nhắn, theo mặc định, cũng không được gửi cho đến khi quá trình hoàn tất. Tuy nhiên, hành vi này có thể được thay đổi bằng cách đặt Thuộc tính SqlConnection.FireInfoMessageEventOnUserErrors thành true. Các tài liệu nêu:

      Khi bạn đặt FireInfoMessageEventOnUserErrors thành true , các lỗi trước đây được coi là ngoại lệ sẽ được xử lý như các sự kiện InfoMessage. Tất cả các sự kiện bắn ngay lập tức và được xử lý bởi xử lý sự kiện. Nếu FireInfoMessageEventOnUserErrors được đặt thành false, thì các sự kiện InfoMessage sẽ được xử lý ở cuối quy trình.

      Nhược điểm ở đây là hầu hết các lỗi SQL sẽ không còn tăng a SqlException. Trong trường hợp này, bạn cần kiểm tra các thuộc tính sự kiện bổ sung được chuyển vào Trình xử lý sự kiện thư. Điều này đúng với toàn bộ kết nối, khiến mọi thứ trở nên phức tạp hơn một chút, nhưng không thể quản lý được.

    • Tất cả các thông báo xuất hiện ở cùng cấp độ không có trường hoặc thuộc tính riêng biệt để phân biệt cái này với cái kia. Thứ tự nhận được chúng phải giống như cách chúng được gửi, nhưng không chắc chắn liệu điều đó có đủ tin cậy hay không. Bạn có thể cần phải bao gồm một thẻ hoặc một cái gì đó mà sau đó bạn có thể phân tích cú pháp. Bằng cách đó bạn ít nhất có thể chắc chắn cái nào là cái nào.


2
Tôi thử nó Sau khi tính toán chuỗi, tôi trả về nó như đơn giản chọn và tiếp tục thủ tục. Vấn đề là nó trả về tất cả các bộ cùng một lúc (tôi cho rằng sau RETURNcâu lệnh). Vì vậy, nó không hoạt động.
Bogdan Bogdanov

2
@BogdanBogdanov Bạn đang sử dụng .NET và SqlConnection? Bạn muốn trả lại bao nhiêu dữ liệu? Kiểu dữ liệu nào? Bạn đã thử một trong hai PRINThoặc RAISERROR WITH NOWAIT?
Solomon Rutzky

Tôi sẽ thử ngay. Chúng tôi sử dụng dịch vụ web .NET.
Bogdan Bogdanov

"Bởi vì nó là một bảng tạm thời toàn cầu, bạn không cần phải lo lắng về mức độ cô lập giao dịch" - điều đó có thực sự chính xác không? Các bảng tạm thời của IIRC, ngay cả các bảng toàn cầu, phải tuân theo các hạn chế ACID giống như bất kỳ bảng nào khác. Bạn có thể chi tiết cách bạn đã kiểm tra hành vi?
David Spillett

@DavidSpillett Bây giờ tôi nghĩ về nó, mức độ cô lập thực sự không phải là vấn đề, và điều tương tự cũng đúng với đề xuất của bạn. Miễn là bảng không được tạo trong Giao dịch. Tôi chỉ cập nhật câu trả lời của tôi với mã ví dụ.
Solomon Rutzky

0

Nếu quy trình được lưu trữ của bạn cần chạy trong nền (tức là không đồng bộ), thì bạn nên sử dụng Nhà môi giới dịch vụ. Thiết lập một chút khó khăn, nhưng sau khi thực hiện xong, bạn sẽ có thể khởi động quy trình được lưu trữ (không chặn) và lắng nghe các thông báo tiến trình trong bao lâu (hoặc ít) như bạn muốn.

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.