Feed on
Posts
Comments
Цък:

Това ще влезе в категория quick checks. Предварително казвам, че (все още) не познавам добре системата, за която става дума.

Днес ми излезе интересен въпрос: една таблица има няколко десетки милиона реда, с данни от 8 години насам. Има колонка с дата, по която има индекс. Колонката няма хистограма. Когато пуснем заявката select count(1) from tab where date_col > sysdate-1, съвсем правилно, се изпозлва съответният индекс, т.е. не сме в тази скучна ситуация “Oracle не ми използва индекса”. Обаче се прави Index Fast Full Scan (wtf???), което естествено, дава незадоволителна производителност:

  1. SQL> SET autot traceonly explain
  2. SQL> SELECT COUNT(1) FROM tab WHERE date_col>SYSDATE-1;
  3.  
  4. Execution Plan
  5. ———————————————————-
  6. Plan hash VALUE: 2096840410
  7.  
  8. —————————————————————————————-
  9. | Id  | Operation             | Name           | Rows  | Bytes | Cost (%CPU)| TIME     |
  10. —————————————————————————————-
  11. |   0 | SELECT STATEMENT      |                |     1 |     8 | 10461   (7)| 00:02:06 |
  12. |   1 |  SORT AGGREGATE       |                |     1 |     8 |            |          |
  13. |*  2 |   INDEX FAST FULL SCAN| I_TAB_DATE_COL |  5038K|    38M| 10461   (7)| 00:02:06 |
  14. —————————————————————————————-
  15.  
  16. Predicate Information (identified BY operation id):
  17. —————————————————
  18.  
  19.    2 - filter("DATE_COL">SYSDATE@!-1)

Според explain plan, използването на IFFS води до cost от ~10000, докато при hint INDEX_SS_ASC cost-а е 15000.

  1. SQL> SELECT /*+INDEX_SS_ASC(t I_TAB_DATE_COL)*/ COUNT(1) FROM tab t WHERE date_col>SYSDATE-1;
  2.  
  3. Execution Plan
  4. ———————————————————-
  5. Plan hash VALUE: 547665643
  6.  
  7. ————————————————————————————
  8. | Id  | Operation         | Name           | Rows  | Bytes | Cost (%CPU)| TIME     |
  9. ————————————————————————————
  10. |   0 | SELECT STATEMENT  |                |     1 |     8 | 15875   (1)| 00:03:11 |
  11. |   1 |  SORT AGGREGATE   |                |     1 |     8 |            |          |
  12. |*  2 |   INDEX RANGE SCAN| I_TAB_DATE_COL |  5038K|    38M| 15875   (1)| 00:03:11 |
  13. ————————————————————————————
  14.  
  15. Predicate Information (identified BY operation id):
  16. —————————————————
  17.  
  18.    2 - access("DATE_COL">SYSDATE@!-1)

При аномалии в cost, особено в елементарни заявки, веднага гледам статистиките, защото в 99.9999% от случаите, там се крие заека.

Статистиките си бяха ОК, събрани са скоро (предната вечер):

  1. SQL> SET autot off
  2. SQL> SELECT TO_CHAR(last_analyzed, 'DD.MM.YYYY HH24:MI:SS')
  3.   FROM dba_tab_statistics
  4.  WHERE owner = 'OWNR'
  5.    AND table_name = 'TAB';  
  6.  
  7. TO_CHAR(LAST_ANALYZED,'DD.MM.Y
  8. —————————————————————————
  9. 07.02.2010 22:03:58

Преди да задълбая в clustering factor и други подробности за индекса, погледнах какво точно пише за тази колонка, та оптимизатора решава, че ще вземе твърде много данни при range scan.

  1. SQL> column low_value format a20
  2. SQL> column high_value format a20
  3. SQL> SELECT low_value, high_value, density
  4.   FROM dba_tab_col_statistics
  5.  WHERE owner = 'OWNR'
  6.    AND table_name = 'TAB'
  7.    AND column_name = 'DATE_COL';
  8.  
  9. LOW_VALUE            HIGH_VALUE              DENSITY
  10. ——————– ——————– ———-
  11. 78660107173A0A       78710208121319       1.1388E-07

Опа… греда. Данните за low_value и high_value са в колонка тип long (ха-ха-ха). С малко търсене намерих в OTN как да видя датата в по-разбираем формат

  1. SQL> SET serveroutput ON
  2. DECLARE
  3.   rv RAW(32) := '78710208121319';
  4.   dt DATE := NULL;
  5. BEGIN
  6.   DBMS_STATS.convert_raw_value(rv, dt);
  7.   DBMS_OUTPUT.put_line(TO_CHAR(dt, 'DD-MM-YYYY hh24:mi:ss'));
  8. END;
  9. /
  10. 08-02-2013 17:18:24
  11.  
  12. PL/SQL PROCEDURE successfully completed.

Грешката е вярна! Оказа се че има няколко бъгливи записа, които стоят години напред. Съответно предиката date_col>SYSDATE-1 връща 3 години данни. При липсата на хистограми оптимизатора няма как да знае, че ще върне няколко десетки/стотици записи - грубата сметка показва, че ще прерови данни за 3 години от общо налични 11 между low_value и high_value.

В случая решението беше да се затрият бъгливите данни. Но по принцип това е идеален пример за ползата от хистограмите.

Еко

Цък:

Поздрав за всички защитници на уличните псета:

“Кучета разкъсаха 13 животни в столичния зоопарк”

Да ви питам, сега, “добри” хора: когато кучетата нападат деца и възрастни, това не ви притеснява чак толкова. А когато нападат животни? Ами сега, кое е по-еко: да защитим псетата да лопатарите?

Уличният добитък, плод на човешкото безхаберие и простотия, не може да бъде оставен да разкъсва каквото си реши. Ако някой иска, да си прибере кучетата в дома си. Ако не - по възможно най-безболезнения начин да минат под ножа. Защото тая гмеж, тия зли зъби и нокти са несъвместими с хуманността. И никой не може да ме убеди, че, разбираш ли, и те душа носят, и те заслужават живот. Вирусите също душа носят. Както и плъховете, и хлебарките.

Единствената файда от уличните кучета е малкото улични котки…

Сняг!

Цък:

Най-после! От кога го чакам!

Тази година в София имаше само студ, най-много да навали 1-2 пръста сняг. А аз искам да има СНЯГ! Истински, който да направи Големото село бяло, чисто и свежо.

И ето, днес вече има. Доживях! Вярно, само десетина сантиметра, но все е нещо. Успява да покрие сивотата и боклуците. Ах, как ми липсваше снега…

Днес нарочно тръгнах малко по-рано за работа, за да мога да се придвижа с метрото до Ректората и после да походя пеша през парка Заимов. Ах, какво удоволствие изпитах! Тесни не-много-отъпкани пътечки във всички посоки, като разорана целина. Бели дървета. И слънце. Топло време, без вятър… идилия! Прекрасно зимно време за разходка, пък била тя и десетина минути. За малко се почувствах като в на излет планината. Ех, защо парк от Младост до центъра…

Пожелавам на всички ви ужасно много УСМИВКИ и ЛЮБОВ днес, защото си заслужава. Днес никой няма право да е намръщен. Защото денят започва със снежно-слънчева усмивка за целия град!

Налбантин

Цък:

Налбантите са хора, които се занимават с подковаване на коне. Това си е било съвсем достойна професия преди стотина години. Бедни и богати, селяни и крале са се възползвали от техните услуги. Добрите налбантите са били уважавани, защото лошо подкования кон може да окуцее и да не върши работа. А това си е проблем.

Занаята, естествено, се е предавал от баща на син. Младите момчета са се учили на тънкостите занаята с години. Да подковаваш кон не е проста работа - живо същество е.

Обаче с навлизането на МПС-тата като превозни и земеделски машини, конете се използват все по-малко. Налбантите остават без работа. Естествено, това се е случвало в продължение на много години, даже десетилетия. Обаче традиционалистите, фамилиите, които много поколения наред се хранят с налбантство, се променят трудно:
- Аз друго не мога! Баща ми се е хранил с това, и дядо ми, и безчет години назад. Владея си занаята добре. Не можете да ме оставите без работа!

И така… пак опираме до субсидии. Може и до запречване на основни междуселски пътища край Пловдив. Защото, макар че години наред работата е намалявала, не е имало някой, който да поеме отговорността, или да осигури достойно алтернативно препитание на налбантите. А те са били силни хора. Все пак по цял ден млатят с чука. Можели са да направят много поразии.

Хайде, в края на годината пак ще си говорим за тютюн.

Преписвач

Цък:

В средновековието е имало и такава работа: преписвач. По манастири и кралски дворове, стотици (ако не и хиляди) хора са си изкарвали хляба с това: да преписват книги, прокламации, обяления и т.н. Това е било високообразован труд. Преписвачите са били сред малцинството от грамотни хора. Не са били богати, но като цяло не са и гладували.

И идва в средата на 15-ти век Йохан Гутенберг и взема че изобретява печатарската преса. Всъщност тя е изобретена над 2 века по-рано от кореец, но той я прави полезна и относително масово-приложима.

Направо е съсипала труда на преписвачите. Вече едно копие на последната прокалмация може да излезе за няколко минути (ако не и секунди). Свещените книги, требниците, житията на светиите и прочее научна литература излизат за часове или дни на копие, а не седмици и месеци. Направо е оставил преписвачите гладни.

Понеже са били високообразовани, вероятно някои от тях са станали печатари. Но понеже не са били инженери, пък и не са били необходими чак толкова хора, много от тях са си останали гладни.

Какво ли би направил профсъюзът на преписвачите, ако имаше такъв? Дали би използвал връзките си в свещенническите среди за да прекара забрана за издаване на книги чрез печатна машина? Или би натискал в кралския двор да осигури прокламациите да продължат да се пишат бавно и на ръка? Може би преписвачите, които не са останали чак пък съвсем без работа, биха искали субсидия за всяка преписана страница, за да са конкурентоспособни на печатарите?

Оф, какво ви занимавам и аз с проблемите на селското стопанство

Цък:

Ако има някой не-чул/не-разбрал, да кажа и аз: пролетната сбирка на БГПО ще се случи в края на април (от 23 до 25 април 2010). И този път организаторите са успели да привлекат звезда от световна величина: самият Tom Kyte. Адски съм благодарен за усилията, които правят - не е лесно да се доведе такъв човек. И се надявам той да не им върже тенекия, както направи Anjo Kolk.

Всъщност с напредването на възрастта (пригответе големи подаръци за наближаващият ми юбилей, благодаря ;-) ), все повече оценявам нашите си “звезди” - примерно Юлиян Дончев. Owen Hughes също е почти местен с третото си идване, a и също е много добър лектор. Както и Пламен, когато реши да говори. Пък и всеки друг, решил да сподели опит или да покаже резултати от неговия research. (Иване, не се ослушвай)

Този път аз ще се включа с една по-нестандартна лекция. Мисля, че това, което съм подготвил, ще бъде интересно не само за DBA, а и за developers, sysadmins, даже и за IT-ориентирани мениджъри. За това пък съм се замислил (и дано да успея) за есенната сбирка да подготвя най-hard Oracle-database-related лекцията от всичко, което съм правил до сега. Ама това е в далечното несигурно бъдеще… Сега ще говоря за performance forecasting. Тази презентация си я подготвям от година и половина и се надявам, че се е получила добре.

Ще се видим там, нали?

Flash-free Metalink

Цък:

Преди 2-3 месеца Oracle потопиха в море от скръб всички честни клиенти, като затвориха старата бърза и удобна версия на Metalink и оставиха само бъгавата неудобна и нелогична Flash версия. Ако е вярно това, че когато те псуват, те сърби най-задната част от тялото, сигурно всички в Oracle Support са подлудили личните си дерматолози. Думите са слаби да опиша само аз какво им мисля, да не говорим за сръбските DBA-и.

Но има искрица в тунела. Днес случайно попадах на HTML версия на омразния My Oracle Support. Освободена от всички тъпи “красоти” на flash версията (които може да са много cool, ама само до петото отваряне). Бърза, спретната, и съвсем легална. Enjoy!

П.П. Между другото, ЧНГ на всички

Цък:

Не става дума за общината или пътната агенция. А за нас, шофьорите.

В прословутия задръстен петък, когато цяла София орева света колко непочистени са улиците, аз пътувах малко под 2 часа до офиса. Честно казано, винаги при първия голям снеговалеж става екшън. Докато зацепи тромавата машина, отговорна за снегопочистването - и то вече наваляло. После за няколко часа все пак се справят и до края на деня основните улици са много добре почтистени. Както беше и тази година.

Не така стои въпросът със стотиците хиляди шофьори, които си мислят, че зимата може да се кара с летни гуми. Примерът е точно то петък. По малкото баирче от Малинов към Цариградско беше брутално задръстване, както на всяко друго място. Колите бяха в 2 колони и придвижването ставаше на етапи - тръгваш, пълзиш, спираш, чакаш и пак тръгваш. Ако можеш.

Точно пред мен имаше един червен Сеат, който след спирането не можа да потегли. Сега, вярно, пътят не беше изблизан до асфалт, ама няма как да разчиташ, че винаги ще е. Те за това има зимни и летни гуми. Та спира сеата по средата на баирчето, и като потегля колоната пред него, той започва да върти гуми. Напред не мръдва, ама яко се унесе на дясно към “другата” лента (в която имаше автобус 76). Като видях какво става, пуснах едни аварийки и слязох да предложа да го бутна (няма как да върна назад - колоната не мърда назад).

Пича, обаче, съвсем рационално отказа моята помощ. Защото вече беше почти опрял в автобуса. Изчака дясната колона да мине достатъчно, че да се източи автобуса и при две свободни “ленти” успя да потегли на зиг-заг. Качи баирчето с постоянно въртене на гуми и обиколки от едната лента в другата.

Аз, естествено, се притесних и за себе си. Все пак на гладък лед и зимните гуми не помагат. Пътят не изглеждаше точно като гладък лед, ама като гледах мъките на сеата… и нали съм спрял на същото място…

Да, ама не. Рендето си потегли съвсем безгрижно. Т.е. пътя не е бил съвсем на стъкло, просто заснежен. И се чудя с кой ли акъл е тръгнал тоя. И пак е късметлия, че закъса на горе. Защото ако гумите те предадат по надолнище, го отнасяш и ти, и невинните пред теб.

* * *

В събота улиците бяха перфектно почистени. Стигнах до работа за 35 минути. Както и днес. Но то нямаше и голямо движение. Явно тия с “цървулите” бяха разбрали по трудния начин, че така няма да стане. Пак. Всяка година при първия сняг улиците се задръстват от тях. Хем то и през прозореца се вижда, че вече не е лято. Ама те да опитат…

* * *

Миналата зима. Имаше доста навалял сняг. Прецених, че ще видя зор да изляза от паркинга пред блока. Извадих от багажника една лопата, която си нося по цяла зима за такива случаи (да е жив и здрав Весо, че ми я подари) и си разринах. Като ме видя една съседка помоли да и дам лопатата, да си почисти и тя пред нейната кола. Аз съвсем кавалерски отидох лично да и разрина. Завързахме лаф и подхвърлих, че ако е с добри гуми няма да има проблем - стигне ли до пътя. Тя каза, че гумите и са много зле.

Зачудих се къде ли е тръгнала в тоя сняг, щом няма добри гуми. След като и разринах, на бегом се върнах до моята кола и се изнизах преди да е запушила пътя.

* * *

Срещу офиса има гумаджийница. Днес има голяма опашка, както беше и в петък и събота. Някои коли не могат да се качат на тротоара да си изчакат реда - то с галошите трудно става… Е сега ли е момента да си сменяш гумите? Сигурно още по-големи са опашките пред тенекеджийниците :(

Цък:

(към част 4)

С предишната част от поредицата показах един малко използван и доста мощен вариант за защита. В повечет случаи той е “прекалено” ограничаващ (харесвам термина scalability inhibitor). Но може да се окаже полезен в случаи, когато едни данни се модифицират от токова много и различни места кода, че никой не ги знае всичките (спагети).

Обратно, когато се знае точно кои парчета от кода ще достъпват данните, се дефинират критични секции. За целта може да използва много интелигентно заключване с помощта на пакета dbms_lock. Този пакет предоставя възможност да се използват познатите ни вградени в Оracle механизми за заключване, за да управляваме достъпа до “наши” си ресурси. Основно предимство пред hand-made заключваниците е, че механизма е доказан, тестван, железен и познат. За да може потребителя да използва този механизъм, трябва да има права за изпълнение на dbms_lock.

Ето малко примерен код:

  1. CREATE OR REPLACE PROCEDURE Process_New_Data IS
  2.   lck_handle NUMBER;
  3.   lck_result BINARY_INTEGER;
  4. BEGIN
  5.   – preventing multiple sessions run the same code
  6.   DBMS_LOCK.ALLOCATE_UNIQUE(lockname => 'New_data_processing', lockhandle => lck_handle);
  7.   lck_result := DBMS_LOCK.REQUEST(lockhandle => lck_handle, lockmode => DBMS_LOCK.X_MODE, timeout => 0,
  8.                                   release_on_commit => FALSE);
  9.   /* possible results:
  10.     0 - Success; 1 - Timeout; 2 - Deadlock; 3 - Parameter error;
  11.     4 - Already own lock specified by id or lockhandle; 5 - Illegal lock handle
  12.   */
  13.   IF lck_result != 0 THEN
  14.     RAISE_APPLICATION_ERROR(-20115, 'Cannot allocate lock New_data_processing: ' || lck_result);
  15.   END IF;
  16.  
  17.   – do wathever we want
  18.   LOOP
  19.     INSERT . . .;
  20.     DELETE . . .;
  21.     COMMIT; – we can do many transactions, the lock is defined with release_on_commit => false
  22.   END LOOP;
  23.   UPDATE . . .;
  24.   COMMIT;
  25.  
  26.   – release the lock
  27.   lck_result := DBMS_LOCK.RELEASE(lockhandle => lck_handle);
  28.  
  29. EXCEPTION
  30.   WHEN OTHERS THEN
  31.     – we should always release the lock!!!
  32.     lck_result := DBMS_LOCK.RELEASE(lockhandle => lck_handle);
  33.     – do whatever error handling we need
  34.     ROLLBACK;
  35.     INSERT INTO Processing_log
  36.       (TIME, module, action, error_msg, extra_data, status)
  37.     VALUES
  38.       (SYSDATE, 'Process_New_Data', NULL,
  39.        DBMS_UTILITY.FORMAT_ERROR_STACK() || CHR(13) ||
  40.        DBMS_UTILITY.FORMAT_ERROR_BACKTRACE(),
  41.        NULL, 'Error');
  42.     COMMIT;
  43.     RAISE;
  44. END;

Основните моменти тук са:

- В началото си дефинираме “ресурс”, който ще заключваме, с помощта на DBMS_LOCK.ALLOCATE_UNIQUE

- После правим опит за заключване на този ресурс с DBMS_LOCK.REQUEST. Тук могат да се използват различни нива на закючване (Shared, Exclusive, SubShared, SubExclusive, Shared SubExclusive).
Може да изчакаме известно време (timeout) - това е много полезно, ако се очаква ресурса да бъде заключван за кратко, след което ние можем да успеем да го получим.
Има и още един много приятен параметър: release_on_commit. С него управляваме дали заключването да се освобождава автоматично при приключване на транзакцията (както правят всички нормални заключваници).
Една важна подробност е, че при неуспех процедурата не връща exception. За това трябва да се обработи резултата, който връща.

- След това може да се прави всичко според бизнес изискванията. Ако заключването е направено с release_on_commit => false, спокойно може да се реализира и код с много транзакции (като в част 1).

- На края ресурса се отключва с DBMS_LOCK.RELEASE. Тук отново при неуспех няма exception, а различен return value.

- Важно е да се подсигури “отключването” на ресурса с exception handler. Ако по някаква причина не го направим и процедурата “гръмне” по средата, той ще си остане заключен до края на сесията.

Цък:

(към част 3)

Основно правило при многопотребителската работа е да си заключваш ресурсите, с които работиш. Едно здраво заключване може да спести много главоболия. За съжаление за да направиш добро заключване, трябва 1) да си осъзнал, че ти трябва, и 2) да знаеш как да го направиш :)

Повечето хора се сещат за select ... for update. Обаче има и други заключваници, които могат да помогнат на съвестния db developer. Единият вариант е Lock table. Ще използвам примера от част 2:

  1. CREATE OR REPLACE PROCEDURE Process_New_Data IS
  2.   – example code - not scalable!!!
  3. BEGIN
  4.   – lock the log table, so none will be able to add/remove batches
  5.   LOCK TABLE batch_log IN EXCLUSIVE MODE;
  6.   – process the loaded batches
  7.   INSERT /*+append*/
  8.   INTO prod_table
  9.     SELECT – do some processing
  10.            batch_id, get_something(column1, column2), calculate_something(column3, column4),
  11.            RANK() over(PARTITION BY column5, column6 ORDER BY column7) rn,
  12.            column8, column9,
  13.       FROM loader_table
  14.      WHERE batch_id IN (SELECT batch_id
  15.                           FROM batch_log
  16.                          WHERE processed = 0);
  17.  
  18.   – delete the rows from loader table
  19.   DELETE FROM loader_table
  20.    WHERE batch_id IN (SELECT batch_id
  21.                         FROM batch_log
  22.                        WHERE processed = 0);
  23.  
  24.   – mark the batches as processed
  25.   UPDATE batch_log
  26.      SET processed = 1
  27.    WHERE processed = 0;
  28.  
  29.   COMMIT;
  30. END;

В случая още в началото на процедурата заключваме таблицата batch_log. Това означава, че никой няма да може да добавя, променя или премахва редове от нея. :
- ако има сесия, която вкарва нови данни, при опит да направи insert - ще чака.
- ако има друга сесия, която изпълнява нашата процедура за обработка/преместване на данни - ще гръмне или ще чака, в зависимост от това дали ще дадем wait на заключването. Това е и една от причините този lock да се прави в самото начало, преди да сме свършили някаква друга работа.

Самото заключване се “отключва” автоматично в с първия commit или rollback. За това точно този подход е неприложим в ситуации като описаната в част 1.

По-лошото е, че този подход е адски не-скалируем. Това е brute force на защитата. Докато не приключим с всичката работа, никой няма да може да работи по тази таблица (освен да select-ва). С други думи, заменили сме един проблем с друг. И сега проблема ще се прояви при натоварване с конкурентни сесии. Може да се каже, че сме по-добре: проблемът ще е ясен, няма да има случайни загуби или дублиране. Но си е проблем.

Интересна дилема: колкото повече (и по-рестриктивни) заключваници използваме, толкова по-зле понася товар едно приложение. Но ако не ги използваме, добрата саклируемост води до грешни данни. Някой да си е помислил, че има просто решение? :)

(към част 5)

Older Posts »