Generator danych do bazy - zastąpienie executeQuery()

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;
    }
1

IMO problem jest poza metodą. Faktycznie jest nieoptymalne użycie preparedStatement, ale w praktyce nie ma to zwykle dużego znaczenia jesli chodzi o szybkość (dla bezpieczeństwa natomiast ma...).
Strzelam, że zła obsługa connection lub coś podobnego. Trzeba by całą pętle zobaczyć.
Ewentualnie brak indeksu na id_towary - dośc nietypowe (klucz główny), ale nauka radziecka notowała takie przypadki.

Poza tym - jak chcesz mieć szybko to nie czytaj z bazy. Ja tam dane trzymam w RAM - i szybko, i pewnie.

0

To jest zadanie z aplikacji bazodanowych, więc całe zadanie opiera się właśnie na odczycie i zapisie danych do bazy danych.
A jeśli chodzi o błąd poza pętlą, to szczerze wątpię żeby tak było. Przetestowałem już wcześniej tą metodę poza pętlą, i kiedy wykonuje się tylko jeden raz to wszystko działa sprawnie.

Tutaj metoda połączenia:

Class.forName("oracle.jdbc.driver.OracleDriver");
Connection baza = DriverManager.getConnection("jdbc:oracle:thin:@IP:orcltp", "nazwa", "haslo");

Wywołanie pętli:

for(int i = 0; i <ilosc_wierszy; i++)
        {    
            Wstaw_dane_do_tabeli_pozycja_faktury(baza, insert_pozycja_faktury);
        }

Metoda Wstaw_dane_do_tabeli_pozycja_faktury, która wywołuje metodę sprawdzającą stan na magazynie:

public static void Wstaw_dane_do_tabeli_pozycja_faktury(Connection baza, PreparedStatement tabela)
    {
        int id_faktury = Wylosuj_unikalny_klucz_obcy();
        int ile_pozycji = Losuj_liczbe_z_przedzialu(1,5);
        
        try
        {
            for(int i =0;i<ile_pozycji;i++)
            {
                int klucz = Wylosuj_klucz_obcy(klucze_obce_dwa);
                int max = Sprawdz_ilosc_magazynowa_towaru(baza, polecenie, klucz);
                int ilosc;
                if(max>=99) ilosc = Losuj_liczbe_z_przedzialu(1, 99);
                else ilosc = Losuj_liczbe_z_przedzialu(1, max);

                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);
        }
        
    }

Oraz aktualna metoda do sprawdzenia tej ilości (selecta dałem jako parametr, tak jak mi wcześniej doradzano):

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

        try
        {
            PreparedStatement prepared = baza.prepareStatement(polecenie+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;
    }
4

Masakra. Po pierwsze naucz się uzywać prepared statementów a nie klej stringów jak zwierze. Podpowiem select ilosc_magazynowa from Towary where id_towaru = ? a następnie zobacz jakie metody ma klasa PreparedStatement. Zrób taki statement RAZ a potem binduj parametry i odpalaj w pętli.

0

Zrobione, teraz metoda wygląda tak:

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;
    }

Tylko kurcze teraz mi się coś spieprzyło w losowaniu, bo wywala mi poniższy błąd. Do bazy zawsze wstawia się tylko jeden wiersz, przy drugim się wysypuje i leci aż do końca.

java.lang.IllegalArgumentException: bound must be positive

Tak jakbym losował liczbę ze złego przedziału.
Ale chciałem przetestować czy ta zmiana przyspieszy program, więc ustawiłem sobie stałą wartość dla ilości. Faktycznie, program wykonuje się szybciej, ale jednak nadal dosyć długo to trwa - 4 minuty.
Chyba, że nadal coś mam źle w tej metodzie?

EDIT:
Dobra zapomniałem z metody wywalić zamknięcie tego statementu. Teraz działa, ale jeśli chodzi o czas, to nadal wygląda to tak samo. Czas wykonania metody to około 7 minut...

1

No dobra, ale teraz pytanie co konkretnie trwa tutaj dużo czasu? Może oprócz tego selecta robisz jeszcze jakis dziwne rzeczy, albo robisz głupoty w stylu lock na tabeli i nie zwalnianie transakcji.
Moja rada: zrób minimalny przykład z jakimś H2 który pokazuje twój problem.

1

A napisz coś może o tym gdzie ta baza stoi? na tym samym komputerze, w tej samej sieci?
Na pewno masz indeksy?
Masz jakiś monitoring javy i tego oracla? CPU/ IO ? Cokolwiek?

Pomysł : zamiast pytania wstaw select 1 from dual i zobacz jak się zmieni wydajność.
Jeśli nadal będzie wolno to raczej nie z powodu braku indeksów czy locków.

0

Baza danych jest uczelniana. Każdy ze studentów ma tu swoje konto. Nie jest lokalna, nie jest w tej samej sieci.
Indeksów nie mam, indeksy mamy dopiero zrobić w następnym zadaniu.
Nie mam monitoringu.
Ale według mnie wszystko jest przez linijkę

ResultSet wyniki = pobierz_sztuki.executeQuery();

Jak zacząłem robić ten program to cały czas dawałem executeQuery. I wszystko wykonywało się po 17 minut. Zacząłem szukać dlaczego tak długo to trwa, natrafiłem na addBatch. No i zastąpiłem executeQuery na addBatch, a dopiero jak wychodziłem z pętli to robiłem executeBatch i nagle z 17 minut wszystko trwało 10 sekund.
Teraz mam podobną sytuację, tylko w przypadku selecta nie robi się czegoś takiego jak addBatch, prawda? Czy się mylę?
Tylko w tej metodzie mam executeQuery. Jeśli ją zakomentuję, program wykonuje się w 8-12 sekund. Jeśli zostawię - ponad 7 minut.

1

W takim razie odpowiedź jest prosta: masz lagi ;-)

0
select 1 from dual

nic nie zmienia :/ Dalej trwa tak długo.

1

W takim razie odpowiedź jest prosta: masz lagi ;-)

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?

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..

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.
0

Czyżby jednak executeQuery?

1

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)
0

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

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.