Raz a dobrze (SQL)

Problem na dziś

Export do bazy pewnych danych. Nazwijmy je „akcesoria”. Aplikacja jest w WinFormsach. Tych „akcesoriów” jest dużo, ok. 1 000. Baza jest umieszczona daleko. Jak daleko? Ping zajmuje 32 ms. Podobnie czasowo zajmuje wykonanie „SELECT 1” (w ten sposób można sprawdzić, czy baza sama z siebie nie ma żadnych problemów).

Rozwiązanie i dyskusja

Aktualnie mamy eleganckie procedury, które służą do wrzucania tych „akcesoriów”. Dodajemy argumenty i wykonuje się szybka procedura, zajmuje jakieś 40ms. Jest chyba dobrze z wydajnością tego zapytania (nie znam się, ale chyba z 32ms na 40ms nie zabije nam aplikacji).

Przykład ładnej procedury wykonywanej w pętli:

        public void SendThemAll()
        {
            using (var connection = new SqlConnection("SomeConnection"))
            {
                using (var command = new SqlCommand("SomeStoredProcedure", connection))
                {
                    command.Parameters.Add("@letter", SqlDbType.NVarChar);

                    var letters = GetAlphabet();

                    connection.Open();

                    foreach (var letter in letters)
                    {
                        command.Parameters["@letter"].Value = letter;

                        // 40ms each query
                        command.ExecuteNonQuery();
                    }
                }
            }
        }

Takie rzeczy są jednak dobre i szybkie w aplikacjach webowych, gdzie od razu mamy bazę i updatujemy zazwyczaj tylko pojedyncze rekordy.
W moim przypadku niestety musimy to zapytanie wykonywać w pętli. 1 000 x 40ms = 40sekund. Nie przeskoczymy tych 40ms. A to oczywiście nie wszystko, bo danych równie dobrze może być 10x więcej.

To jest chyba ten przypadek, gdzie trzeba wysłać jedno wielkie zapytanie z posklejanych 1 000 razy pojedynczych INSERTÓW. Jeden ogromny string. Bardzo nieładne, ale jedyny sposób by udało się zmieścić w sensownym czasie. Nie jest to aplikacja internetowa, więc żadne SQL Injection i pochodne nam nie grożą.

Przykład okropnego, lecz pomocnego sklejania stringów

        public void SendThemAllInOneUglyString()
        {
            var sb = new StringBuilder();

            var letters = GetAlphabet();

            foreach (var letter in letters)
            {
                sb.AppendFormat(
                    "INSERT INTO Accessories(name) VALUES('{0}');",
                    letter);

                // Below sql syntax does not work in SQL Server 2005 (only 2008 and newer)
                // INSERT INTO Accessories(name) VALUES ('A'), ('B')
                // so in the code I use more general approach shown below
                // INSERT INTO Accessories(name) VALUES ('A')
                // INSERT INTO Accessories(name) VALUES ('B')
            } 
            
            using (var connection = new SqlConnection("SomeConnection"))
            {
                using (var command = new SqlCommand(sb.ToString(), connection))
                {
                    connection.Open();

                    // this query takes about 1 second
                    command.ExecuteNonQuery();
                }
            }
        }

Powyższe zapytanie wykona się w około 1 sekundę, a o to właśnie nam chodziło.

Reklamy
Ten wpis został opublikowany w kategorii Uncategorized i oznaczony tagami , , , . Dodaj zakładkę do bezpośredniego odnośnika.