Bagaimana cara mendapatkan respons dari prosedur tersimpan sebelum selesai?

8

Saya perlu mengembalikan hasil sebagian (sesederhana pilih) dari prosedur Disimpan sebelum selesai.

Apakah mungkin untuk melakukan itu?

Jika ya, bagaimana cara melakukannya?

Jika tidak, ada solusi?

EDIT: Saya punya beberapa bagian dari prosedur. Pada bagian pertama saya menghitung beberapa string. Saya menggunakannya nanti dalam prosedur untuk membuat operasi tambahan. Masalahnya adalah bahwa string dibutuhkan oleh penelepon sesegera mungkin. Jadi saya perlu menghitung string itu dan meneruskannya kembali (entah bagaimana, dari pilih misalnya) dan kemudian terus bekerja. Penelepon mendapatkan string berharga jauh lebih cepat.

Penelepon adalah Layanan Web.

Bogdan Bogdanov
sumber
Dengan asumsi kunci tabel lengkap belum terjadi atau transaksi eksplisit belum dinyatakan, Anda harus dapat menjalankan SELECT dalam sesi terpisah tanpa masalah.
Steve Mangiameli
Secara umum ini hanya cara saya melihatnya sekarang, tetapi saya tidak berpikir itu akan jauh lebih cepat (juga ada masalah lain), @SteveMangiameli
Bogdan Bogdanov
Membaginya menjadi dua SP? Lewati output dari yang pertama ke yang kedua.
paparazzo
Bukan solusi yang sangat cepat, itulah sebabnya kami mengacaukannya, @ Paparazzi
Bogdan Bogdanov

Jawaban:

11

Anda mungkin mencari RAISERRORperintah dengan NOWAITopsi.

Per sambutan :

RAISERROR dapat digunakan sebagai alternatif untuk CETAK untuk mengembalikan pesan ke aplikasi panggilan.

Ini tidak mengembalikan hasil dari SELECTpernyataan, tetapi itu akan membiarkan Anda meneruskan pesan / string kembali ke klien. Jika Anda ingin mengembalikan subset cepat dari data yang Anda pilih maka Anda mungkin ingin mempertimbangkan FASTpetunjuk kueri.

Menentukan bahwa kueri dioptimalkan untuk pengambilan cepat number_rows pertama. Ini adalah bilangan bulat tidak negatif. Setelah number_rows pertama dikembalikan, kueri melanjutkan eksekusi dan menghasilkan set hasil lengkapnya.

Ditambahkan oleh Shannon Severance dalam komentar:

Dari Penanganan Kesalahan dan Transaksi di SQL Server oleh Erland Sommarskog:

Berhati - hatilah, bagaimanapun, bahwa beberapa API dan alat dapat buffer di sisi mereka, sehingga membatalkan efek dari WITH NOWAIT.

Lihat artikel sumber untuk konteks penuh.

Erik
sumber
FASTmemecahkan masalah bagi saya dalam masalah di mana saya perlu menyinkronkan pelaksanaan prosedur tersimpan dan kode C # untuk memperburuk dan mereproduksi kondisi balapan. Lebih mudah untuk mengkonsumsi hasil secara terprogram daripada menggunakan sesuatu seperti RAISERROR(). Ketika saya mulai membaca jawaban Anda, sepertinya Anda mengatakan bahwa itu tidak dapat dilakukan SELECT, jadi mungkin itu bisa diklarifikasi?
binki
5

UPDATE: Lihat jawaban strutzky (di atas ) dan komentar untuk setidaknya satu contoh di mana ini tidak berperilaku seperti yang saya harapkan dan jelaskan di sini. Saya harus bereksperimen / membaca lebih lanjut untuk memperbarui pemahaman saya ketika waktu mengizinkan ...

Jika pemanggil Anda berinteraksi dengan basis data secara tidak sinkron atau berulir / multi-proses, sehingga Anda dapat membuka sesi kedua saat sesi pertama masih berjalan, Anda bisa membuat tabel untuk menampung sebagian data dan memutakhirkannya seiring prosedur berlangsung. Ini kemudian dapat dibaca oleh sesi kedua dengan tingkat isolasi 1 set transaksi untuk memungkinkannya membaca perubahan yang tidak dikomit:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM progress_table

1: sesuai komentar dan pembaruan berikutnya dalam jawaban srutzky, pengaturan tingkat isolasi tidak diperlukan jika proses yang dipantau tidak dibungkus dalam transaksi, meskipun saya cenderung menetapkannya karena kebiasaan dalam keadaan seperti itu karena tidak menyebabkan membahayakan saat tidak diperlukan dalam kasus ini

Tentu saja jika Anda dapat memiliki beberapa proses yang beroperasi dengan cara ini (yang kemungkinan jika server web Anda menerima pengguna secara bersamaan dan sangat jarang hal itu tidak terjadi) Anda harus mengidentifikasi informasi kemajuan untuk proses ini dengan cara tertentu . Mungkin lulus prosedur UUID yang baru dicetak sebagai kunci, tambahkan itu ke tabel progres, dan bacalah dengan:

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

Saya telah menggunakan metode ini untuk memantau proses manual yang berjalan lama di SSMS. Saya tidak dapat memutuskan apakah itu "bau" terlalu banyak bagi saya untuk mempertimbangkan menggunakannya dalam produksi meskipun ...

David Spillett
sumber
1
Ini adalah salah satu opsi tetapi saya tidak suka saat ini. Saya harap beberapa opsi lain akan muncul.
Bogdan Bogdanov
5

OP telah mencoba mengirim beberapa set hasil (bukan MARS) dan telah melihat bahwa memang menunggu Prosedur Tersimpan untuk menyelesaikan sebelum mengembalikan set hasil apa pun. Dengan mengingat situasi itu, berikut adalah beberapa opsi:

  1. Jika data Anda cukup kecil untuk ditampung dalam 128 byte, kemungkinan besar Anda bisa menggunakan SET CONTEXT_INFOyang seharusnya membuat nilai itu terlihat SELECT [context_info] FROM [sys].[dm_exec_requests] WHERE [session_id] = @SessionID;. Anda hanya perlu menjalankan kueri cepat sebelum menjalankan Prosedur Tersimpan SELECT @@SPID;dan mengambilnya SqlCommand.ExecuteScalar.

    Saya baru saja menguji ini dan itu berhasil.

  2. Mirip dengan saran @ David untuk menempatkan data ke dalam tabel "progres", tetapi tanpa perlu dipusingkan dengan masalah pembersihan atau konkurensi / pemisahan proses:

    1. Buat yang baru Guiddi dalam kode aplikasi dan berikan sebagai parameter ke Prosedur Tersimpan. Simpan Panduan ini dalam sebuah variabel karena akan digunakan beberapa kali.
    2. Dalam Prosedur Tersimpan, buat Tabel Sementara Global menggunakan Panduan itu sebagai bagian dari nama tabel, sesuatu seperti CREATE TABLE ##MyProcess_{GuidFromApp};. Tabel dapat memiliki kolom apa pun dari tipe data apa pun yang Anda butuhkan.
    3. Kapan pun Anda memiliki data, masukkan ke dalam Tabel Temp Global itu.

    4. Dalam kode aplikasi, mulai mencoba untuk membaca data, tetapi membungkus SELECTdalam IF EXISTSsehingga tidak akan gagal jika tabel belum dibuat belum:

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

    Dengan String.Format(), Anda bisa mengganti {0}dengan nilai dalam variabel Guid. Uji apakah Reader.HasRows, dan jika benar maka bacalah hasilnya, panggil lagi Thread.Sleep()atau apa saja untuk kemudian polling lagi.

    Manfaat:

    • Tabel ini diisolasi dari proses lain karena hanya kode aplikasi yang mengetahui nilai Guid spesifik, karenanya tidak perlu khawatir tentang proses lain. Proses lain akan memiliki tabel temp global pribadi sendiri.
    • Karena ini adalah sebuah tabel, semuanya diketik dengan kuat.
    • Karena ini adalah tabel sementara, ketika sesi yang menjalankan Prosedur Tersimpan berakhir, tabel tersebut akan dibersihkan secara otomatis.
    • Karena ini adalah tabel sementara global :
      • itu dapat diakses oleh Sesi lain, seperti meja permanen
      • itu akan bertahan akhir dari sub-proses di mana ia dibuat (yaitu EXEC/ sp_executesqlpanggilan)


    Saya telah menguji ini dan berfungsi seperti yang diharapkan. Anda dapat mencobanya sendiri dengan contoh kode berikut.

    Di satu tab permintaan, jalankan yang berikut, dan kemudian sorot 3 baris di blok-komentar dan jalankan itu:

    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;
    */
    

    Buka tab "Pesan" dan salin GUID yang dicetak. Kemudian, buka tab kueri lain dan jalankan yang berikut, letakkan GUID yang Anda salin dari tab Pesan Sesi lain ke inisialisasi variabel di baris 1:

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

    Terus memukul F5. Anda akan melihat 1 entri untuk 10 detik pertama, dan kemudian 2 entri untuk 10 detik berikutnya.

  3. Anda dapat menggunakan SQLCLR untuk melakukan panggilan kembali ke aplikasi Anda melalui Layanan Web atau cara lain.

  4. Anda mungkin dapat menggunakan PRINT/ RAISERROR(..., 1, 10) WITH NOWAITuntuk mengirim string kembali dengan segera, tetapi ini akan sedikit rumit karena masalah berikut:

    • Output "Pesan" terbatas pada salah satu VARCHAR(8000)atauNVARCHAR(4000)
    • Pesan tidak dikirim dengan cara yang sama dengan hasil. Untuk menangkap mereka, Anda perlu mengatur pengendali acara. Dalam hal ini, Anda bisa membuat variabel sebagai kumpulan statis untuk mendapatkan pesan yang akan tersedia untuk semua bagian kode. Atau mungkin cara lain. Saya memiliki satu atau dua contoh dalam jawaban lain di sini yang menunjukkan cara menangkap pesan dan akan menautkannya nanti ketika saya menemukannya.
    • Pesan, secara default, juga tidak dikirim sampai proses selesai. Perilaku ini, bagaimanapun, dapat diubah dengan menyetel SqlConnection.FireInfoMessageEventOnUserErrors Property ke true. Dokumentasi menyatakan:

      Ketika Anda mengatur FireInfoMessageEventOnUserErrors menjadi true , kesalahan yang sebelumnya diperlakukan sebagai pengecualian sekarang ditangani sebagai peristiwa InfoMessage. Semua acara langsung menyala dan ditangani oleh pengendali acara. Jika FireInfoMessageEventOnUserErrors diatur ke false, maka peristiwa InfoMessage ditangani di akhir prosedur.

      Kelemahan di sini adalah bahwa sebagian besar kesalahan SQL tidak akan lagi meningkatkan SqlException. Dalam hal ini, Anda perlu menguji properti acara tambahan yang diteruskan ke Message Event Handler. Ini berlaku untuk seluruh koneksi, yang membuat segalanya sedikit rumit, tetapi tidak dapat dikelola.

    • Semua pesan muncul di tingkat yang sama tanpa bidang atau properti terpisah untuk membedakan satu dari yang lain. Urutan penerimaannya harus sama dengan cara pengirimannya, tetapi tidak yakin apakah itu cukup dapat diandalkan. Anda mungkin perlu menyertakan tag atau sesuatu yang kemudian dapat Anda parsing. Dengan begitu Anda setidaknya bisa memastikan yang mana yang mana.

Solomon Rutzky
sumber
2
Saya mencobanya. Setelah menghitung string saya mengembalikannya secara sederhana, pilih dan lanjutkan prosedur. Masalahnya adalah ia mengembalikan semua set pada waktu yang sama (saya kira setelah RETURNpernyataan). Jadi tidak berfungsi.
Bogdan Bogdanov
2
@BogdanBogdanov Apakah Anda menggunakan .NET dan SqlConnection? Berapa banyak data yang ingin Anda sampaikan kembali? Tipe data apa? Sudahkah Anda mencoba PRINTatau tidak RAISERROR WITH NOWAIT?
Solomon Rutzky
Saya akan coba sekarang. Kami menggunakan .NET Web Service.
Bogdan Bogdanov
"Karena ini adalah tabel sementara global, Anda tidak perlu khawatir tentang tingkat isolasi transaksi" - apakah itu benar? Tabel sementara IIRC, bahkan yang global, harus tunduk pada batasan ACID yang sama dengan tabel lainnya. Bisakah Anda merinci bagaimana Anda menguji perilaku itu?
David Spillett
@ DavidSpillett Sekarang saya berpikir tentang hal itu, tingkat isolasi benar-benar bukan masalah, dan hal yang sama berlaku untuk saran Anda. Selama tabel tidak dibuat dalam suatu Transaksi. Saya baru saja memperbarui jawaban saya dengan kode contoh.
Solomon Rutzky
0

Jika prosedur tersimpan Anda perlu dijalankan di latar belakang (mis. Tidak serempak), maka Anda harus menggunakan Pialang Layanan. Agak sulit untuk diatur, tetapi setelah selesai, Anda akan dapat memulai prosedur yang tersimpan (non-pemblokiran) dan mendengarkan pesan kemajuan selama (atau sesedikit) yang Anda inginkan.

Serge
sumber