Generator danych do bazy - zastąpienie executeQuery()

Generator danych do bazy - zastąpienie executeQuery()
AN
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 4 lata
  • Postów:50
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.

Kopiuj
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
Zobacz pozostały 1 komentarz
AN
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.
AN
Próbowałem, absolutnie nic to nie zmienia
AK
oczywiście przed pętlą?
AN
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);
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:36 minut
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4709
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.


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 4x, ostatnio: jarekr000000
Zobacz pozostały 1 komentarz
PI
@jarekr000000: Zawsze polecasz trzymać dane w RAM, ale co z jego persystencją? Brak prądu, restart serwera i elo z danych
jarekr000000
Mam ups, a przycisk reset zalepiłem. Dane bezpieczne.
PI
xd a na serio?
jarekr000000
A na serio to w każdym systemie inaczej, w zależności od warunków. Ale w bazie danych dane trzymam tylko jak nie ma innego wyjścia. Najlepiej w event log + RAM, czasem inne systemy + RAM, czasem baza danych insert only. A czasem jak wszyscy - w tabelkach - jak to mało wazny system o niewielkim ruchu.
pedegie
https://github.com/OpenHFT/Chronicle-Queue na Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz jestem w stanie zapisać 10 mln 40 bajtowych wiadomości (replikując jeden raz) na sekundę. Zdaję sobię sprawę, że nie każdy będzie real time liczył kursy walut ale możemy skorzystać z bardziej high-level api (nawet coś z jsonem zdaje się jest) i nadal otrzymać zadowalający speed bez kolosa typu relacyjna baza danych
AN
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 4 lata
  • Postów:50
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:

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

Wywołanie pętli:

Kopiuj
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:

Kopiuj
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):

Kopiuj
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;
    }
AK
Dlaczego tabela.setString(5, "200,00"); ? Kolejny argument, że z projektem bazy jest coś nie-ten-tego
AN
Bo sobie testowałem na stałych wartościach jak tą metodę zakomentowałem i póki co tak zostawiłem. Ale to nie ma żadnego znaczenia.
Shalom
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
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.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 1x, ostatnio: Shalom
AN
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 4 lata
  • Postów:50
0

Zrobione, teraz metoda wygląda tak:

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

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

edytowany 3x, ostatnio: annonymouzinho
AN
Dobra, błąd pewnie przez to, że zamykam statement.
Shalom
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
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.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 1x, ostatnio: Shalom
jarekr000000
To oracle, więc na h2 pewnie będzie śmigać :-)
Shalom
tak też może być :D
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:36 minut
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4709
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.


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 1x, ostatnio: jarekr000000
AN
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 4 lata
  • Postów:50
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ę

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

jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:36 minut
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4709
1

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


jeden i pół terabajta powinno wystarczyć każdemu
AN
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 4 lata
  • Postów:50
0
Kopiuj
select 1 from dual

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

jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:36 minut
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4709
1

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


jeden i pół terabajta powinno wystarczyć każdemu
AN
Ale jak mam lagi? I dlatego metoda wykonuje się 7 minut? Przecież wtedy reszta rzeczy też by się strasznie długo robiła
jarekr000000
lagi na połączeniu do bazy danych. każde execute trwa relatywnie długo. Jak robisz batchUpdate to zbierasz x wywołań w jedno i płacisz mniejszą "karę". Tak po prawdzie to zgaduje. To że problemem jest łacze z bazą danych wydaje się w tej chwili najbardziej prawdopodobnym wyjaśnieniem, ale niekoniecznie musi tak być (tylko Ty chyba nie masz narzędzi, żeby sprawdzić gdzie jest problem).
Shalom
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
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?


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
AN
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 4 lata
  • Postów:50
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

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

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

Kopiuj
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:

Kopiuj
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:

Kopiuj
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
AN
Sprawdziłem ile trwa executeBatch po tej pętli - 0.85 sekundy
AK
Nadal karmisz nas fragmentami kodu
AN
Zaraz zrobię edycje tego posta. Wrzucę kod, bo przeprowadziłem testy dla różnych konfiguracji.
Shalom
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
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.

"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 1x, ostatnio: Shalom
AN
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.
AN
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 4 lata
  • Postów:50
0

Czyżby jednak executeQuery?

PU
  • Rejestracja:ponad 9 lat
  • Ostatnio:5 miesięcy
  • Postów:59
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)
AN
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ę?
PU
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 :)
AN
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ć.
AN
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 4 lata
  • Postów:50
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.