Berapa jumlah maksimum variabel lokal yang dapat berpartisipasi dalam operasi SET?

11

Saya memiliki prosedur tersimpan yang berisi logika bisnis. Di dalamnya saya memiliki sekitar 1.609 variabel (jangan tanya saya mengapa, ini adalah bagaimana mesin bekerja). Saya mencoba SETsebuah variabel dengan nilai gabungan dari semua variabel lainnya. Akibatnya selama pembuatan saya mendapatkan kesalahan:

Msg 8631, Level 17, Status 1, Prosedur XXX, Jalur YYY Galat internal: Batas tumpukan server telah tercapai. Silakan cari potensi sarang yang dalam di kueri Anda, dan cobalah menyederhanakannya.

Saya tahu bahwa kesalahan ini disebabkan oleh jumlah variabel yang perlu saya gunakan dalam SEToperasi. Saya dapat melakukan tugas dengan membaginya menjadi dua.

Pertanyaan saya, adakah batasan dalam bidang ini? Saya memeriksa, tetapi saya tidak menemukannya.

Kami memeriksa kesalahan yang dijelaskan dalam KB ini , tetapi ini bukan kasus kami. Kami tidak menggunakan CASEekspresi apa pun di dalam kode kami. Kami menggunakan variabel sementara itu untuk menyiapkan daftar nilai yang harus diganti menggunakan fungsi CLR. Kami memperbarui SQL Server kami ke SP3 CU6 (terbaru terbaru), tetapi kami masih mengalami kesalahan.

Bogdan Bogdanov
sumber

Jawaban:

16

Msg 8631, Level 17, Status 1, Jalur xxx
Galat internal: Batas tumpukan server telah tercapai.
Silakan cari potensi sarang yang dalam di kueri Anda, dan cobalah menyederhanakannya.

Kesalahan ini terjadi dengan daftar penyatuan tugas panjang SETatau SELECTvariabel karena cara SQL Server mem-parsing dan mengikat jenis pernyataan ini - sebagai daftar bersambung dari gabungan dua input.

Misalnya, SET @V = @W + @X + @Y + @Zterikat ke pohon bentuk:

ScaOp_Arithmetic x_aopAdd
    ScaOp_Arithmetic x_aopAdd
        ScaOp_Arithmetic x_aopAdd
            ScaOp_Identifier @W 
            ScaOp_Identifier @X 
        ScaOp_Identifier @Y 
    ScaOp_Identifier @Z 

Setiap elemen gabungan setelah dua yang pertama menghasilkan tingkat tambahan bersarang dalam representasi ini.

Jumlah ruang tumpukan yang tersedia untuk SQL Server menentukan batas akhir untuk bersarang ini. Ketika batas terlampaui, pengecualian dinaikkan secara internal, yang akhirnya menghasilkan pesan kesalahan yang ditunjukkan di atas. Contoh tumpukan panggilan proses ketika kesalahan dilemparkan ditunjukkan di bawah ini:

Jejak tumpukan

Repro

DECLARE @SQL varchar(max);

SET @SQL = '
    DECLARE @S integer, @A integer = 1; 
    SET @S = @A'; -- Change to SELECT if you like

SET @SQL += REPLICATE(CONVERT(varchar(max), ' + @A'), 3410) +';'; -- Change the number 3410

-- SET @S = @A + @A + @A...
EXECUTE (@SQL);

Ini adalah batas mendasar karena cara beberapa rangkaian ditangani secara internal. Itu mempengaruhi SETdan SELECTvariabel pernyataan tugas sama.

Solusinya adalah membatasi jumlah pertemuan yang dilakukan dalam satu pernyataan. Ini juga biasanya akan lebih efisien, karena mengkompilasi pohon permintaan yang dalam bersifat intensif sumber daya.

Paul White 9
sumber
5

Terinspirasi oleh @ Paulus 's jawaban , saya melakukan penelitian dan menemukan bahwa sementara itu benar bahwa ruang stack tidak membatasi jumlah concatenations, dan bahwa ruang stack adalah fungsi memori yang tersedia dan dengan demikian bervariasi, dua hal berikut juga benar :

  1. ada cara untuk menjejalkan rangkaian tambahan ke dalam satu pernyataan, DAN
  2. menggunakan metode ini untuk melampaui batasan ruang stack awal, batas logis aktual (yang tampaknya tidak bervariasi) dapat ditemukan

Pertama, saya mengadaptasi kode tes Paul untuk merangkai string:

DECLARE @SQL NVARCHAR(MAX);

SET @SQL = N'
    DECLARE @S VARCHAR(MAX), @A VARCHAR(MAX) = ''a''; 
    SET @S = @A';

SET @SQL += REPLICATE(CONVERT(NVARCHAR(MAX), N' + @A'), 3312) + N';';

-- SET @S = @A + @A + @A...
SET @SQL += N'SELECT DATALENGTH(@S) AS [Chars In @S];';
EXECUTE (@SQL);

Dengan tes ini, nilai tertinggi yang bisa saya dapatkan ketika menjalankan laptop saya yang tidak terlalu bagus (hanya 6 GB RAM) adalah:

  • 3311 (mengembalikan 3312 total karakter) menggunakan SQL Server 2017 Express Edition LocalDB (14.0.3006)
  • 3512 (mengembalikan 3513 total karakter) menggunakan SQL Server 2012 Developer Edition SP4 (KB4018073) (11.0.7001)

sebelum mendapatkan error 8631 .

Selanjutnya, saya mencoba mengelompokkan concatenations dengan menggunakan tanda kurung sehingga operasi akan menyatukan beberapa grup concatenations. Sebagai contoh:

SET @S = (@A + @A + @A + @A) + (@A + @A + @A + @A) + (@A + @A + @A + @A);

Melakukan itu saya bisa melampaui batas sebelumnya 3312 dan 3513 variabel. Kode yang diperbarui adalah:

DECLARE @SQL VARCHAR(MAX), @Chunk VARCHAR(MAX);

SET @SQL = '
    DECLARE @S VARCHAR(MAX), @A VARCHAR(MAX) = ''a''; 
    SET @S = (@A+@A)';

SET @Chunk = ' + (@A' + REPLICATE(CONVERT(VARCHAR(MAX), '+@A'), 42) + ')';

SET @SQL += REPLICATE(CONVERT(VARCHAR(MAX), @Chunk), 762) + ';';

SET @SQL += 'SELECT DATALENGTH(@S) AS [Chars In @S];';

-- PRINT @SQL; -- for debug

-- SET @S = (@A+@A) + (@A + @A...) + ...
EXECUTE (@SQL);

Nilai maksimum (untuk saya) sekarang adalah yang digunakan 42untuk yang pertama REPLICATE, dengan demikian menggunakan 43 variabel per grup, dan kemudian menggunakan 762untuk yang kedua REPLICATE, dengan demikian menggunakan 762 grup dengan masing-masing 43 variabel. Kelompok awal adalah hard-coded dengan dua variabel.

Output sekarang menunjukkan bahwa ada 32.768 karakter dalam @Svariabel. Jika saya memperbarui grup awal menjadi (@A+@A+@A)bukan hanya (@A+@A), maka saya mendapatkan kesalahan berikut:

Msg 8632, Level 17, Negara 2, Jalur XXXXX
Kesalahan internal: Batas layanan ekspresi telah tercapai. Harap cari kemungkinan ekspresi kompleks dalam kueri Anda, dan cobalah untuk menyederhanakannya.

Perhatikan bahwa nomor kesalahan berbeda dari sebelumnya. Sekarang: 8632 . DAN, saya memiliki batas yang sama ini apakah saya menggunakan contoh SQL Server 2012 atau contoh SQL Server 2017.

Hal ini mungkin kebetulan bahwa atas-batas di sini - 32.768 - adalah max kapasitas dari SMALLINT( Int16NET) IF mulai 0(nilai max adalah 32.767 tapi array dalam banyak bahasa pemrograman / sebagian besar berbasis 0).

Solomon Rutzky
sumber
0

Sekarang, ini hanya kehabisan memori dengan kata lain, karena operasi prosedur tersimpan dilakukan dalam memori dan transistor perangkat keras yang tersedia atau Virtual Page Memory yang tersedia untuk SQL penuh!

Jadi pada dasarnya Stack Overflow di SQL Server.

Sekarang, pertama-tama cobalah untuk menyederhanakan prosesnya, karena Kami tahu Anda membutuhkan 1609 Variabel,

Tetapi apakah Anda membutuhkan semua variabel pada saat yang sama?

Kami dapat mendeklarasikan dan menggunakan variabel di mana diperlukan.

Sebagai contoh:

Declare @var1 int, @Var2 int @Var3 int, .... , @var1000 int; -- Here assume Thousand Variables are declared

Declare @Tot Int;
SET @Tot = 0;
if(True)
Begin
    SET @TOT = @TOT+ VAR1 + VAR2 + .... + VAR1000; -- This might fail; 
End

Tetapi jika kita mencoba ini dalam satu lingkaran dengan menambahkan

Declare @Tot Int;
SET @Tot = 0;
DECLARE @i int, @Count int;
SET @i = 1;
SET @Count = 1609;
WHILE (@i <= @Count)
BEGIN
   DECLARE @SQL NVARCHAR(128);
   SET @SQL = 'SET @TOT = @TOT+ VAR'+ cast(@i as nvarchar);
   EXEC (@SQL);
   SET @i = @i + 1;
END

Catatan: Ini akan menggunakan lebih banyak CPU, dan membutuhkan lebih banyak waktu dalam perhitungan.

Sekarang ini akan lambat, tetapi manfaatkan penggunaan memori yang lebih sedikit.

Saya harap ini membantu, Silakan kirim kueri Anda sehingga kami dapat memahami skenario yang tepat.

MarmiK
sumber
-4

Menggunakan pernyataan SELECT dan bukannya SET dapat meningkatkan kinerja dan keterbacaan, dan dapat membantu Anda mengatasi kesalahan yang dinyatakan. Jadi alih-alih:

SET @a = 1
SET @b = 2
SET @c = @e + 2*@d

Anda dapat melakukan:

SELECT @a = 1, @b = 2, @c = @e + 2 * @d

Dan atur ketiga nilai dalam satu pernyataan.

Matthew Sontum
sumber