Generator danych do bazy - zastąpienie executeQuery()

Odpowiedz Nowy wątek
2020-03-25 15:36

Rejestracja: 1 rok temu

Ostatnio: 22 godziny temu

0

Cześć, piszę aktualnie program w Javie, który ma generować dane do bazy. Ogólnie kończę już pracę nad tym, jednak mam jeszcze jeden problem... W zasadzie jest to chyba ostatnia rzecz, którą muszę dokończyć. Może przejdę do problemu.
W bazie mam tabelę z towarami oraz jest też tabela pozycja faktury.
W tabeli pozycja faktury mam do zapełnienia pola takie jak: ilość danego towaru. Żeby generator generował realne dane, takie jakie mogłyby być w rzeczywistości to muszę najpierw sprawdzić, ile danego towaru jest na stanie zanim wylosuję liczbę.
Działa to u mnie tak:

  1. Losuję sobie id z tabeli towary
  2. Sprawdzam ilość sztuk na magazynie danego towaru
  3. Losuję ilość sztuk, tak żeby nie przekroczyć faktycznej wartości.
    Wszystko fajnie pięknie mam zrobione. Jednak w czym jest problem? A w tym, że przez metodę, która sprawdza ilość sztuk na magazynie danego towaru, 1000 wierszy do około 10 tabel wstawia się ponad 7 minut. Bez tej metody jest to 10 sekund. Czyli sama metoda wykonuje się jakieś 7 minut.
    A dlaczego się tak dzieje?
    Dzieje się tak, dlatego że metodę wykonuję w pętli. Czyli 1000x wykonuje się executeQuery(). I tutaj nie wiem jak sobie z tym poradzić.
    Przy insertach nie było problemu, po prostu używałem metody addBatch(), a po zakończonej pętli executeBatch().
    Ale przy selectach o ile wiem nie da się zrobić czegoś takiego.
    Macie jakieś pomysły jak można by rozwiązać ten problem?

Poniżej znajduje się metoda na sprawdzenie tej ilości sztuk na magazynie. Wywoływana jest ona w pętli, przykładowo dla 1000 wykonań, metoda wykonuje się jakieś 7 minut.

public static int Sprawdz_ilosc_magazynowa_towaru(Connection baza, int klucz)
    {
        int ilosc_magazynowa = 0;

        try
        {
            PreparedStatement prepared = baza.prepareStatement("select ilosc_magazynowa from Towary where id_towaru="+klucz);
            prepared.setFetchSize(4001);
            ResultSet wyniki = prepared.executeQuery();
            if(wyniki.next()) ilosc_magazynowa = wyniki.getInt("ilosc_magazynowa");

            wyniki.close();
            prepared.close();

        }
        catch(Exception e)
        {

        }

        return ilosc_magazynowa;
    }
edytowany 1x, ostatnio: annonymouzinho, 2020-03-25 15:49
Wbrew pozorom preparowania - powinieneś z tego mieć zysk - ale nie masz, bo nie używasz parametru, tylko sklejasz. - AnyKtokolwiek 2020-03-25 15:41
Ale muszę to sklejać. Dla każdego towaru ilość jest inna, więc w zależności jaki to był towar muszę pobrać dla niego ilość sztuk, która znajduje się na magazynie. - annonymouzinho 2020-03-25 15:51
Próbowałem, absolutnie nic to nie zmienia - annonymouzinho 2020-03-25 16:02
oczywiście przed pętlą? - AnyKtokolwiek 2020-03-25 16:02
Tak, przed pętlą stworzyłem string z selectem. Następnie w pętli wywołałem metodę, dodałem do niej tego strina jako dodatkowy parametr. I w statemencie teraz mam cos takiego: (polecenie+klucz); - annonymouzinho 2020-03-25 16:06

Pozostało 580 znaków

2020-03-26 00:27
Moderator

Rejestracja: 16 lat temu

Ostatnio: 35 minut temu

1

Jeśli select 1 nadal trwa długo to znaczy że problem jest na poziomie czasu połączenia do bazy. Nawiązanie połączenia, wysłanie i odebranie odpowiedzi widocznie zabiera u ciebie większość czasu. Spróbuj to moze odpalić z jakiegoś uczelnianego shella?


Masz problem? Pisz na forum, nie do mnie. Nie masz problemów? Kup komputer...

Pozostało 580 znaków

2020-03-27 10:09

Rejestracja: 1 rok temu

Ostatnio: 22 godziny temu

0

Niestety nie mam takiej możliwości :/ Ale jedna rzecz mi dalej spokoju nie daje. Jeśli mam 9 podobnych pętli, w których wstawiam dane do bazy i tutaj wszystko robi się jak należy, to dlaczego przy tej jednej nagle czas się tak wydłuża? Na dodatek jeśli sobie ręcznie wywołałem tą metodę 10 razy, to wszystko było w porządku.

EDIT:
W poniższej konfiguracji program śmiga normalnie, wykonuje się wszystko w 8-10 sekund

public static void Wstaw_dane_do_tabeli_pozycja_faktury2(Connection baza, PreparedStatement tabela)
    {
        int id_faktury = Wylosuj_unikalny_klucz_obcy();
        int ile_pozycji = Losuj_liczbe_z_przedzialu(1,5);

        for(int i =0;i<ile_pozycji;i++)
        { 
            int klucz = Wylosuj_klucz_obcy(klucze_obce_dwa);
            int ilosc = Losuj_liczbe_z_przedzialu(1, 99);

            try
            {
                tabela.setString(1, null);
                tabela.setInt(2, id_faktury);
                tabela.setInt(3, klucz);
                tabela.setInt(4, ilosc);
                //tabela.setString(5, Policz_cene_pozycji(baza, klucz, ilosc));
                tabela.setString(5, "200,00");
                tabela.addBatch();
            }
            catch(Exception e)
            {
                System.out.println(e);
            }
        }
    }

W następnej konfiguracji zmieniłem tylko cenę pozycji. Zamiast podawać ją na sztywno, teraz chcę aby program ją wyliczył. I tutaj już jest niespodzianka, bo program wykonuje się w 3-4 minuty.

public static void Wstaw_dane_do_tabeli_pozycja_faktury2(Connection baza, PreparedStatement tabela)
    {
        int id_faktury = Wylosuj_unikalny_klucz_obcy();
        int ile_pozycji = Losuj_liczbe_z_przedzialu(1,5);

        for(int i =0;i<ile_pozycji;i++)
        { 
            int klucz = Wylosuj_klucz_obcy(klucze_obce_dwa);
            int ilosc = Losuj_liczbe_z_przedzialu(1, 99);

            try
            {
                tabela.setString(1, null);
                tabela.setInt(2, id_faktury);
                tabela.setInt(3, klucz);
                tabela.setInt(4, ilosc);
                tabela.setString(5, Policz_cene_pozycji(baza, klucz, ilosc));
                //tabela.setString(5, "200,00");
                tabela.addBatch();
            }
            catch(Exception e)
            {
                System.out.println(e);
            }
        }
    }

No a jeśli zastosuję konfigurację, którą chcę finalnie użyć aby generator działał odpowiednio, to już jest w ogóle kombo, bo wszystko trwa 8 minut...

public static void Wstaw_dane_do_tabeli_pozycja_faktury2(Connection baza, PreparedStatement tabela)
    {
        int id_faktury = Wylosuj_unikalny_klucz_obcy();
        int ile_pozycji = Losuj_liczbe_z_przedzialu(1,5);

        for(int i =0;i<ile_pozycji;i++)
        { 
            int klucz = Wylosuj_klucz_obcy(klucze_obce_dwa);
            int max = Sprawdz_ilosc_magazynowa_towaru(baza, klucz);
            int ilosc;
            if(max>=99) ilosc = Losuj_liczbe_z_przedzialu(1, 99);
            else ilosc = Losuj_liczbe_z_przedzialu(1, max);

            try
            {
                tabela.setString(1, null);
                tabela.setInt(2, id_faktury);
                tabela.setInt(3, klucz);
                tabela.setInt(4, ilosc);
                tabela.setString(5, Policz_cene_pozycji(baza, klucz, ilosc));
                tabela.addBatch();
            }
            catch(Exception e)
            {
                System.out.println(e);
            }
        }
    }

Poniżej metoda na obliczanie ceny pozycji:

public static String Policz_cene_pozycji(Connection baza, int klucz, int ilosc)
    {
        double cena_katalogowa = 0.0;
        double ilosc_towaru = (double)ilosc;
        try
        { 
            pobierz_cene_katalogowa.setInt(1, klucz);
            pobierz_cene_katalogowa.setFetchSize(4001);
            ResultSet wyniki = pobierz_cene_katalogowa.executeQuery();
            if(wyniki.next()) cena_katalogowa = wyniki.getDouble("cena_katalogowa");

            wyniki.close();
            //pobierz_cene_katalogowa.close(); - zamykam ją gdy wyjdę z pętli
        }
        catch(Exception e)
        {

        }
        return po_przecinku.format(cena_katalogowa * ilosc_towaru);
    }

A tutaj sprawdzenie ilości na stanie:

public static int Sprawdz_ilosc_magazynowa_towaru(Connection baza, int klucz)
    {
        int ilosc_magazynowa = 0;

        try
        {  
            pobierz_sztuki.setInt(1, klucz);
            pobierz_sztuki.setFetchSize(4001);
            ResultSet wyniki = pobierz_sztuki.executeQuery();
            if(wyniki.next()) ilosc_magazynowa = wyniki.getInt("ilosc_magazynowa");

            wyniki.close();
            //pobierz_sztuki.close();

        }
        catch(Exception e)
        {

        }

        return ilosc_magazynowa;
    }

Ja już nie mam pojęcia co robię źle..

edytowany 4x, ostatnio: annonymouzinho, 2020-03-27 12:30
Sprawdziłem ile trwa executeBatch po tej pętli - 0.85 sekundy - annonymouzinho 2020-03-27 10:53
Nadal karmisz nas fragmentami kodu - AnyKtokolwiek 2020-03-27 12:18
Zaraz zrobię edycje tego posta. Wrzucę kod, bo przeprowadziłem testy dla różnych konfiguracji. - annonymouzinho 2020-03-27 12:19

Pozostało 580 znaków

2020-03-27 15:45
Moderator

Rejestracja: 16 lat temu

Ostatnio: 35 minut temu

1
  1. Używanie całkiem statycznej wartości to słaby benchmark bo optymalizacje w ogóle to zredukują do zera. Jeśli w ogóle to testuj to jakimś random() a nie statyczna wartością.
  2. Użyj profilera zamiast zgadywać, zobacz CO DOKŁADNIE zabiera czas, bo zaraz się okaże że w sumie w ogóle co innego. Szczególnie martwią mnie te pootwierane statementy, a nie widzę nigdzie informacji co robisz z transakcjami. Może w ogóle jest tak, że robić jakieś dziwne locki które wszystko spowalniaja.

Masz problem? Pisz na forum, nie do mnie. Nie masz problemów? Kup komputer...
edytowany 1x, ostatnio: Shalom, 2020-03-27 15:47
Jeśli chodzi o statementy, to po zakończonej pętli od razu je zamykam. Mam jeszcze pytanie, czyli metoda executeQuery, która jest wykonywana w pętli w obu metodach (tj. Sprawdz_ilosc_magazynowa_towaru oraz Policz_cene_pozycji) nie są raczej przyczyną tak powolnego działania? Bo obie metody są identyczne, jeśli jedną z nich zakomentuję to program wykonuje się połowe szybciej niż jeśli są one obie używane. A jeśli nie ma żadnej to wszystko śmiga szybciutko. Co do tego profilera to postaram się to ogarnąć, bo pierwszy raz się z czymś takim spotykam. - annonymouzinho 2020-03-27 16:02

Pozostało 580 znaków

2020-03-27 16:30

Rejestracja: 1 rok temu

Ostatnio: 22 godziny temu

0

Czyżby jednak executeQuery?

Pozostało 580 znaków

2020-03-27 21:21

Rejestracja: 4 lata temu

Ostatnio: 2 minuty temu

Podejdźmy do twojego problemu trochę inaczej. Ile masz tych towarów w bazie? ile jest instancji twojej aplikacji? Jeżeli odpowiedzi brzmią tak:

  1. Mało (do 100k/1mln? ile ramu przydzielasz aplikacji)
  2. Jedna
    Wczytaj te dane do ramu (hashmapy?) i nie rób selectów tylko sprawdzaj stan magazynowy w pamięci. Ominiesz całe IO (które pewnie generuje narzut)
1. W bazie mam 500 towarów 2. Mam jedną instancję. Tylko żeby wczytać dane do hashmapy, to i tak muszę wykonać te selecty, czy się mylę? - annonymouzinho 2020-03-27 21:39
Przed wykonaniem pętli wczytaj do HashMapy dane: select ilosc_magazynowa from Towary Za klucz weź id_towaru, a za wartość weź ilość magazynową, następnie w pętli wyciągaj wartości z HashMapy - będzie dużo szybciej :) - pustypawel 2020-03-27 22:00
Dziękuję bardzo :) Na szybko przetestowałem, bo już późna pora i chyba wszystko działa. Jutro jeszcze to dokładnie sprawdzę i dam znać. - annonymouzinho 2020-03-28 01:08

Pozostało 580 znaków

2020-03-28 15:59

Rejestracja: 1 rok temu

Ostatnio: 22 godziny temu

0

Dziękuję wszystkim za pomoc, szczególnie Tobie, @pustypawel , teraz wszystko działa bardzo szybko :)
Temat można zamknąć.

Pozostało 580 znaków

Odpowiedz

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0