Oracle Optimizer Statistics: DBMS_STATS কীভাবে সঠিকভাবে করবেন
আঠারো বছরে 'query গতকাল দ্রুত ছিল, আজ ধীর' - এমন প্রায় প্রতিটি টিকিট আমি খুঁজতে গিয়ে একটি জিনিসেই পৌঁছেছি: optimizer statistics। Oracle optimizer ঠিক ততটাই ভালো যতটা তাকে আপনার ডেটা সম্পর্কে দেওয়া সংখ্যাগুলো ভালো, আর সেই সংখ্যাগুলোই - statistics - একটি SQL স্টেটমেন্টকে একটি execution plan-এ পরিণত করে। এগুলো ঠিক থাকলে optimizer সাধারণত plan-ও ঠিক করে। এগুলো ভুল, stale বা অনুপস্থিত হলে সে অনুমান করে - বাজেভাবে। এই গাইড ব্যাখ্যা করে statistics আসলে কী, DBMS_STATS দিয়ে কীভাবে সেগুলো ঠিকঠাক গ্যাদার ও ম্যানেজ করবেন, আর কীভাবে খারাপ stats-কে এক রাতে production plan ধ্বংস করা থেকে থামাবেন।
মূল কথাগুলো
- Cost-based optimizer আপনার ডেটা সম্পর্কে statistics ব্যবহার করে plan বেছে নেয় - টেবিলের row সংখ্যা, কলামের distinct value, index selectivity, আর histogram।
- ভুল বা stale statistics হঠাৎ plan বদল আর 'গতকাল দ্রুত ছিল' regression-এর সবচেয়ে সাধারণ মূল কারণ।
- DBMS_STATS ব্যবহার করুন, প্রাচীন ANALYZE কখনও নয়, আর বেসলাইন হিসেবে AUTO_SAMPLE_SIZE ও স্বয়ংক্রিয় stats job-কে প্রাধান্য দিন।
- Histogram skewed ডেটা বর্ণনা করে (একটি status কলাম যার ৯৯% 'CLOSED'); এগুলো প্রচুর সাহায্য করে কিন্তু bind-peeking-এর চমকও ঘটাতে পারে।
- ~১০% row বদলালে stats 'stale' হয়ে যায়; স্বয়ংক্রিয় job সেগুলো আবার গ্যাদার করে, তবে বড় load-এর পরপরই হাতে গ্যাদার দরকার।
- ভোলাটাইল বা যত্ন করে টিউন করা টেবিলে statistics lock করুন, আর নতুন stats production plan-এ আঘাত হানার আগে টেস্ট করতে pending statistics ব্যবহার করুন।

১. Statistics আসলে কী
Oracle যখন একটি SQL স্টেটমেন্ট parse করে, তখন cost-based optimizer-কে ঠিক করতে হয় সেটি কীভাবে চালাবে: কোন index (যদি থাকে), কোন join order, কোন join method, পুরো টেবিল scan করবে নাকি কয়েকটি row খুঁজবে। প্রতিটি ধাপে কতগুলো row তৈরি হবে তা অনুমান করে সে এই সিদ্ধান্তগুলো নেয় - আর অনুমান করে statistics দিয়ে।
মূল statistics-গুলো আপনার ডেটা সম্পর্কে সহজ কিছু সংখ্যা: একটি টেবিলে কতগুলো row আছে, কতগুলো block দখল করে, একটি কলামে কতগুলো distinct value আছে, high ও low value কী, আর প্রতিটি index কতটা selective। এগুলো থেকে optimizer cardinality হিসাব করে - প্রতিটি ধাপের অনুমিত row সংখ্যা - এবং যে plan-কে সবচেয়ে সস্তা মনে করে সেটি বেছে নেয়।
গুরুত্বপূর্ণ অন্তর্দৃষ্টি: plan বাছার সময় optimizer কখনও আপনার আসল ডেটার দিকে তাকায় না। সে শুধু statistics-এর দিকে তাকায়। Statistics যদি বলে একটি টেবিলে ১,০০০ row আছে অথচ আসলে সেখানে ১ কোটি row, তবে optimizer আত্মবিশ্বাসের সঙ্গে এমন একটি plan বেছে নেবে যা মারাত্মক ভুল - আর সে নিশ্চিত থাকবে যে সে ঠিক করেছে।
২. DBMS_STATS দিয়ে Statistics গ্যাদার করা
প্রথমেই যে নিয়মটি গুরুত্বপূর্ণ: DBMS_STATS ব্যবহার করুন, পুরনো ANALYZE কমান্ড কখনও নয়। ANALYZE এখনও backward compatibility-র জন্য আছে কিন্তু এমন statistics তৈরি করে যা আধুনিক optimizer পুরোপুরি বিশ্বাস করে না, আর নিচের ফিচারগুলো তাতে নেই।
-- Gather stats for one table (and its indexes), Oracle-recommended defaults
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(
ownname => 'SALES',
tabname => 'ORDERS',
estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
method_opt => 'FOR ALL COLUMNS SIZE AUTO',
cascade => TRUE,
degree => 4);
END;
/
-- Whole schema
EXEC DBMS_STATS.GATHER_SCHEMA_STATS('SALES', degree=>4);
দুটি default এখানে বড় কাজ করছে। AUTO_SAMPLE_SIZE Oracle-কে sample বেছে নিতে দেয় - আধুনিক ভার্সন একটি দ্রুত, নির্ভুল অ্যালগরিদম ব্যবহার করে যা পুরনো নির্দিষ্ট শতাংশের চেয়ে অনেক বেশি পড়ে, তাই কারণ ছাড়া estimate_percent হার্ডকোড করবেন না। METHOD_OPT => 'FOR ALL COLUMNS SIZE AUTO' Oracle-কে সিদ্ধান্ত নিতে দেয় কোন কলামে histogram দরকার, সেগুলো আসলে কীভাবে ব্যবহৃত হচ্ছে তার ভিত্তিতে।
৩. স্বয়ংক্রিয় Statistics Job
10g থেকে Oracle maintenance window-তে একটি স্বয়ংক্রিয় statistics-গ্যাদারিং job চালায়, যা সেই অবজেক্টগুলোর stats আবার গ্যাদার করে যাদের ডেটা উল্লেখযোগ্যভাবে বদলেছে। বেশিরভাগ টেবিলের জন্য, বেশিরভাগ সময়ে, এটাই যথেষ্ট - আর এটি বন্ধ করে দেওয়া একটি ক্লাসিক নিজের পায়ে কুড়াল মারা।
-- Is the auto job enabled?
SELECT client_name, status FROM dba_autotask_client
WHERE client_name = 'auto optimizer stats collection';
-- What does 'significantly changed' mean? ~10% of rows by default:
SELECT table_name, stale_stats, last_analyzed
FROM dba_tab_statistics
WHERE owner='SALES' AND stale_stats='YES';
Oracle প্রতিটি টেবিলের বিপরীতে DML ট্র্যাক করে এবং মোটামুটি ১০% row বদলালে statistics-কে stale চিহ্নিত করে। auto job তখন stale-গুলো refresh করে। যে ফাঁকটি এটি ঢাকতে পারে না তা হলো timing: একটি বিশাল রাতের load রাত ২টায় শেষ হয়, রিপোর্টিং চলে ভোর ৬টায়, কিন্তু auto job চলে রাত ১০টায় - তাই রিপোর্টগুলো এমন stats-এ চলে যারা জানেই না গত রাতের ডেটার অস্তিত্ব আছে। পরের সেকশনটি ঠিক সেই জন্যই।
৪. বড় পরিবর্তনের পর গ্যাদার করুন - Job-এর জন্য অপেক্ষা নয়
যেকোনো অপারেশন যা একটি টেবিলকে ব্যাপকভাবে বদলায় - একটি bulk load, একটি বড় purge, একটি partition exchange, একটি migration - তার পরপরই job-এর অংশ হিসেবে statistics গ্যাদার করুন, পরের maintenance window পর্যন্ত optimizer-কে অন্ধ রেখে দেওয়ার বদলে।
-- End of a nightly load job:
BEGIN
-- ... load data ...
DBMS_STATS.GATHER_TABLE_STATS('SALES','ORDERS_STG',
estimate_percent=>DBMS_STATS.AUTO_SAMPLE_SIZE);
END;
/
এই একটি অভ্যাসই 'পরদিন সকালের' পারফরম্যান্স আগুনের বড় একটি অংশ ঠেকিয়ে দেয়। গতকালের statistics নিয়ে বসে থাকা একটি সদ্য-load-করা টেবিল হলো সেই পাঠ্যপুস্তকীয় কারণ যেখানে একটি রিপোর্ট হঠাৎ একশ কোটি row full-scan করে - যা ORA-01555-এর পেছনের batch-window timing সমস্যা আর পারফরম্যান্স টিউনিং গাইড-এর পদ্ধতির সঙ্গে ঘনিষ্ঠভাবে সম্পর্কিত।

৫. Histogram: Skewed ডেটা সামলানো
মৌলিক কলাম statistics ধরে নেয় ডেটা সমানভাবে ছড়ানো। বাস্তব ডেটা তা কদাচিৎ। ভাবুন একটি ORDER_STATUS কলাম যার ৯৯% 'CLOSED' আর ১% 'OPEN'। histogram ছাড়া optimizer ধরে নেয় প্রতিটি status সমান সম্ভাব্য এবং সেই কলামে filter করা প্রতিটি query-র অনুমান ভুল করে।
একটি histogram optimizer-কে আসল বিন্যাস জানায় - যে 'OPEN' বিরল আর একটি index-এর যোগ্য, আর 'CLOSED' সাধারণ আর একটি full scan-এ ভালোভাবে সেবা পায়। Oracle এগুলো স্বয়ংক্রিয়ভাবে তৈরি করে (SIZE AUTO) এমন কলামে যেগুলো একইসঙ্গে skewed এবং predicate-এ ব্যবহৃত।
-- See which columns have histograms
SELECT column_name, histogram, num_distinct
FROM dba_tab_col_statistics
WHERE owner='SALES' AND table_name='ORDERS';
Histogram শক্তিশালী কিন্তু একটি বিখ্যাত জটিলতা নিয়ে আসে: bind variable peeking। Optimizer প্রথম bind value-তে উঁকি দেয়, তার উপযোগী একটি plan বানায়, আর ভিন্ন value নিয়ে পরের execution-গুলোতেও সেই plan পুনরায় ব্যবহার করে - তাই বিরল 'OPEN'-এর জন্য অপ্টিমাইজ করা একটি query সাধারণ 'CLOSED'-এর জন্য খারাপ plan-এ চলতে পারে, বা উল্টোটা। আধুনিক ভার্সনে Adaptive Cursor Sharing এটি কিছুটা প্রশমিত করে, কিন্তু যখন দেখবেন একটি query ভিন্ন ইনপুটের জন্য বিরাট ভিন্ন runtime দেখাচ্ছে, তখন skew সঙ্গে একটি histogram সঙ্গে bind - সেটাই সাধারণ সন্দেহভাজন।
৬. যখন তাজা Stats অবস্থা খারাপ করে: স্থিতিশীলতার টুল
মাঝে মাঝে উল্টো সমস্যা আঘাত করে: statistics ঠিক ছিল, একটি regather সেগুলো সামান্য বদলাল, আর একটি জরুরি plan খারাপ কিছুতে উল্টে গেল। যেসব টেবিলের আচরণ আপনি যত্ন করে টিউন করেছেন - বা যেগুলো এতটাই ভোলাটাইল যে 'নির্ভুল' stats-এর কোনো মানে নেই - সেগুলোতে আপনি নিয়ন্ত্রণ নিতে পারেন।
৬.১ Statistics lock করা
-- Freeze stats on a volatile staging/queue table
EXEC DBMS_STATS.LOCK_TABLE_STATS('SALES','ORDER_QUEUE');
-- (the auto job then skips it; unlock with UNLOCK_TABLE_STATS)
Locking ঠিক সেই ছোট ভোলাটাইল টেবিলগুলোর জন্য (একটি queue যা সারাদিন ০ থেকে ১,০০,০০০ row-এর মধ্যে দোলে) যেখানে আপনি একবার প্রতিনিধিত্বমূলক stats সেট করে সেগুলোই রাখেন, আর সেসব টেবিলের জন্য যেখানে plan গড়ে তুলতে আপনি ইচ্ছাকৃতভাবে stats সেট করেছেন।
৬.২ Pending statistics: commit করার আগে টেস্ট
-- Gather into a 'pending' area WITHOUT affecting live plans
EXEC DBMS_STATS.SET_TABLE_PREFS('SALES','ORDERS','PUBLISH','FALSE');
EXEC DBMS_STATS.GATHER_TABLE_STATS('SALES','ORDERS');
-- Test in your session against the pending stats
ALTER SESSION SET optimizer_use_pending_statistics = TRUE;
-- ... run and check the plans ...
-- Happy? Publish them. Not happy? Delete them.
EXEC DBMS_STATS.PUBLISH_PENDING_STATS('SALES','ORDERS');
একটি সংবেদনশীল সিস্টেমে নতুন stats আনার পেশাদার উপায় হলো pending statistics: সেগুলো গ্যাদার করুন, তারা যে plan তৈরি করে তা যাচাই করুন, আর শুধু তখনই সেগুলো live করুন - production regather-এর পর আর আঙুল ক্রস করে বসে থাকা নয়।
৭. Statistics-এর সাধারণ ভুল
- auto job বন্ধ করে দেওয়া কারণ 'একবার এটি একটি plan বদল ঘটিয়েছিল', তারপর আর কখনও হাতে গ্যাদার না করা - ফলে প্রতিটি টেবিল ধীরে ধীরে stale, ভুল stats-এর দিকে সরে যায়। সেই একটি regression বেসলাইন বা locked stats দিয়ে সারান; job রাখুন।
- পুরনো স্ক্রিপ্টে ANALYZE ব্যবহার। সব জায়গায় DBMS_STATS দিয়ে বদলে দিন।
- ছোট একটি sample হার্ডকোড করা (
estimate_percent=>1) ২০০৫ সালের কোনো ব্লগ থেকে কপি করে - AUTO_SAMPLE_SIZE এখন দ্রুততর ও বেশি নির্ভুল। - bulk load-এর পর গ্যাদার না করা - 'পরদিন সকালের' regression-এর একক বৃহত্তম কারণ।
- 'reset' করতে stats মুছে ফেলা - stats-হীন একটি টেবিল dynamic sampling বা বন্য অনুমান বাধ্য করে; মুছে ফেলার বদলে তাজা stats গ্যাদার করুন।
৮. একটি বাস্তব কেস: যে রিপোর্ট প্রতি সোমবার ভেঙে পড়ত
একটি ডিস্ট্রিবিউশন ক্লায়েন্টের একটি sales রিপোর্ট ছিল যা বেশিরভাগ দিন সেকেন্ডে চলত কিন্তু প্রতি সোমবার ৪০ মিনিট ধরে হামাগুড়ি দিত। প্যাটার্নটাই ছিল সূত্র: সপ্তাহান্তের একটি বড় ডেটা load রবিবার রাতে শেষ হতো, স্বয়ংক্রিয় stats job সর্বশেষ চলেছিল শনিবার, আর তাই সোমবারের রিপোর্ট এমন statistics-এর বিপরীতে অপ্টিমাইজ হতো যারা বিশ্বাস করত নতুন-সবচেয়ে-বড় partition-টি প্রায় খালি - তাই সে কয়েকটি row-এর জন্য বানানো একটি nested-loop plan বেছে নিয়ে সেটি লক্ষ লক্ষ row-এর বিপরীতে চালাত।
সমাধানটি ছিল load job-এ একটি লাইন: সপ্তাহান্তের load শেষ হওয়ার ঠিক পরপরই সংশ্লিষ্ট partition-এ AUTO_SAMPLE_SIZE দিয়ে statistics গ্যাদার করা। সোমবারের ধীরগতি উধাও হয়ে গেল। আমরা একটি ছোট, অতি-ভোলাটাইল lookup টেবিলেও stats lock করলাম যা মাঝে মাঝে plan উল্টে দিচ্ছিল, আর তাতে একবার প্রতিনিধিত্বমূলক value সেট করলাম। দুটি লক্ষ্যভেদী পরিবর্তন, শূন্য parameter অনুমান - কারণ নির্ণয়টি statistics থেকে শুরু হয়েছিল, যেখানে query-র রহস্য প্রায় সবসময়ই শেষ হয়।

৯. Plan Flip যে Stats-এর দোষ তা প্রমাণ করা: DBMS_XPLAN ফরেনসিক
একটি query যখন হঠাৎ ধীর হয়ে যায়, তখন মানুষ তর্ক করে। অ্যাপ্লিকেশন টিম ডেটাবেজকে দোষ দেয়, DBA কোডকে দোষ দেয়, আর মিটিং চক্কর খেতে থাকে। আমি এই তর্কগুলো শেষ করি দুটি plan পাশাপাশি রেখে - AWR history থাকলে এতে মিনিট দশেকের বেশি লাগে না।
-- 1. Did the plan actually change? One row per snapshot per plan:
SELECT snap_id, plan_hash_value,
ROUND(elapsed_time_delta/1e6/NULLIF(executions_delta,0),1) AS sec_per_exec
FROM dba_hist_sqlstat
WHERE sql_id = '&sql_id'
ORDER BY snap_id;
-- 2. Print the good plan and the bad plan, compare line by line
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_AWR('&sql_id', &good_hash));
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_AWR('&sql_id', &bad_hash));
-- 3. On the live cursor: what did the optimizer estimate vs reality?
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('&sql_id', NULL, 'ALLSTATS LAST'));
ধাপ ১ সাধারণত এক স্ক্রিনেই গল্পটা বলে দেয়: plan hash 3728490112 সপ্তাহের পর সপ্তাহ ০.৪ সেকেন্ডে চলছিল, তারপর একটি নতুন hash আর ১৯০ সেকেন্ড - শুরুটা ঠিক সেই snapshot থেকে যা গত রাতের load বা regather-এর ঠিক পরের।
ধাপ ৩-এ এসে statistics স্বীকারোক্তি দেয়। E-Rows (optimizer যা অনুমান করেছিল) আর A-Rows (আসলে যা ফিরে এসেছিল) তুলনা করুন। যখন একটি ধাপ ১২টি row অনুমান করে ২৪ লাখ row ফেরত দেয়, তখন optimizer এমন সংখ্যা নিয়ে কাজ করছিল যা আর টেবিলটিকে বর্ণনা করে না - আর DBA_TAB_STATISTICS-এর LAST_ANALYZED সাধারণত সেই ডেটা পরিবর্তনের আগের তারিখ দেখাবে, যে পরিবর্তনটি সব ভেঙেছে।
এই অভ্যাসটিই একটি নির্ণয়কে অনুমান থেকে আলাদা করে। আপনি যখন বলতে পারেন "plan বদলেছে snap 41213-তে, অনুমানটা পাঁচ মাত্রা (order of magnitude) দূরে, আর stats-গুলো load-এরও আগের" - তখন আর কেউ তর্ক করে না; আপনি শুধু সারিয়ে ফেলেন।
১০. সিটবেল্ট: SQL Plan Baseline আর Fixed Stats
ভালো statistics ম্যানেজমেন্ট plan flip কমায়; পুরোপুরি দূর করতে পারে না। যে মুষ্টিমেয় স্টেটমেন্টের regress হওয়া ব্যবসা সত্যিই সইতে পারে না, সেগুলোর জন্য আমি একটি সিটবেল্ট যোগ করি: একটি SQL Plan Baseline, যা জানা-ভালো plan-টিকে পিন করে রাখে।
-- Capture the good plan from the cursor cache into a baseline
DECLARE
n PLS_INTEGER;
BEGIN
n := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(
sql_id => '&sql_id',
plan_hash_value => &good_hash);
END;
/
SELECT sql_handle, plan_name, enabled, accepted
FROM dba_sql_plan_baselines;
এরপর থেকে optimizer নতুন plan খুঁজে পেতেই পারে, কিন্তু plan-টি যাচাই ও accept না হওয়া পর্যন্ত সেটি ব্যবহার করতে পারবে না - নতুন প্রার্থীরা রাত ২টায় live হয়ে যাওয়ার বদলে রিভিউয়ের লাইনে দাঁড়ায়। আমি baseline রাখি শুধু ব্যবসায়িকভাবে জরুরি SQL-এর একটি ছোট তালিকায়। শত শত স্টেটমেন্ট পিন করলে সিটবেল্টটি একটি স্ট্রেটজ্যাকেটে পরিণত হয়, যা প্রকৃত উন্নতিগুলোকেও আড়াল করে দেয়।
অন্য স্থিতিশীলকারীটি সেই টেবিলগুলোর জন্য, যেখানে 'নির্ভুল' statistics পাওয়াই অসম্ভব: staging আর queue টেবিল যা সকাল ৯:০০-তে খালি আর ৯:০৫-এ পাঁচ লাখ row ধরে। এগুলোতে গ্যাদার করা একটি লটারি - job যে মুহূর্তের অবস্থা ধরে ফেলে সেটাই স্থায়ী সত্য হয়ে যায়। আমি একবারই, পূর্ণ অবস্থার জন্য, প্রতিনিধিত্বমূলক statistics সেট করি আর সেগুলো lock করে দিই:
EXEC DBMS_STATS.SET_TABLE_STATS('SALES','ORDER_STG',
numrows => 500000, numblks => 12000);
EXEC DBMS_STATS.LOCK_TABLE_STATS('SALES','ORDER_STG');
সবসময় ব্যস্ত অবস্থার জন্য সেট করুন, কখনও খালি অবস্থার জন্য নয়। ৫,০০,০০০ row-এর জন্য বানানো একটি plan ৫০টি row-এর বিপরীতে গ্রহণযোগ্যভাবেই চলে; কিন্তু শূন্য row-এর জন্য বানানো আর ৫,০০,০০০ row-এর বিপরীতে চালানো একটি nested-loop plan-ই সেই ৪০ মিনিটের batch job, যা আপনাকে মাঝরাতে page করে। (আসল global temporary table 12c-তে সহজ হয়ে গেছে - প্রতিটি session ডিফল্টেই নিজস্ব প্রাইভেট GTT statistics রাখে, যা পুরনো GTT plan-দুর্দশার বেশিরভাগটাই চুপচাপ শেষ করে দিয়েছে।)
আর যে প্যাটার্নটি পুরো লেখাটিকে এক সুতোয় বাঁধে - আমি যে-কোনো batch job লিখি বা রিভিউ করি তার কঙ্কাল:
- Staging টেবিলটি truncate বা purge করুন।
- ডেটা load করুন।
- যা এইমাত্র load হলো তাতে statistics গ্যাদার করুন (বা locked প্রতিনিধিত্বমূলক stats-এর ওপর নির্ভর করুন)।
- শুধু তারপরই read ও transform ধাপ শুরু করুন।
Statistics-এর জায়গা load job-এর ভেতরে - নিজের ক্যালেন্ডার মেনে চলা কোনো maintenance window-র হাতে ছেড়ে দেওয়া নয়।
সাধারণ জিজ্ঞাসা (FAQ)
Oracle-এ optimizer statistics কী?
এগুলো আপনার ডেটা সম্পর্কে সংরক্ষিত সংখ্যা ও বিন্যাস - টেবিলের row ও block সংখ্যা, কলামের distinct value, high/low value, index selectivity আর histogram - যেগুলো cost-based optimizer row সংখ্যা অনুমান করতে আর execution plan বেছে নিতে ব্যবহার করে। Plan বাছার সময় optimizer কখনও আপনার আসল ডেটা পড়ে না; এটি পুরোপুরি এই statistics-এর ওপর নির্ভর করে।
ANALYZE নাকি DBMS_STATS ব্যবহার করব?
সবসময় DBMS_STATS। পুরনো ANALYZE কমান্ড কেবল backward compatibility-র জন্য টিকে আছে এবং এমন statistics তৈরি করে যা আধুনিক optimizer পুরোপুরি ব্যবহার করে না, আর এতে AUTO_SAMPLE_SIZE, histogram নিয়ন্ত্রণ, locking ও pending statistics-এর মতো ফিচার নেই। সব জায়গায় ANALYZE-এর বদলে DBMS_STATS ব্যবহার করুন।
Statistics গ্যাদার করার পর আমার query ধীর হলো কেন?
নতুন statistics optimizer-এর অনুমান বদলে দিয়ে একটি plan-কে খারাপ plan-এ উল্টে দিতে পারে - বিশেষত histogram আর bind variable-সহ skewed ডেটায়। নতুন stats প্রকাশের আগে টেস্ট করতে pending statistics ব্যবহার করুন, যত্ন করে টিউন করা টেবিলে statistics lock করুন, আর জানা-ভালো plan পিন করতে SQL Plan Baseline বিবেচনা করুন।
Histogram কী আর কখন দরকার হয়?
Histogram skewed কলাম বিন্যাস বর্ণনা করে - যেমন একটি status কলাম যার ৯৯% একটিই value। এটি optimizer-কে বিরল value (index-এর যোগ্য) আর সাধারণ value (full-scan-এ ভালো) আলাদাভাবে বিবেচনা করতে দেয়। Oracle এগুলো স্বয়ংক্রিয়ভাবে তৈরি করে METHOD_OPT SIZE AUTO দিয়ে, এমন কলামে যেগুলো একইসঙ্গে skewed এবং predicate-এ ব্যবহৃত।
auto job-এর ওপর নির্ভর না করে কখন হাতে statistics গ্যাদার করব?
যেকোনো অপারেশনের ঠিক পরে যা টেবিলকে ব্যাপকভাবে বদলায় - bulk load, বড় purge, partition exchange, migration। স্বয়ংক্রিয় job maintenance window-তে চলে আর ~১০% পরিবর্তনে stats-কে stale চিহ্নিত করে, তাই এটি রাতের load আর সকালের রিপোর্টিংয়ের মাঝের ফাঁক ঢাকতে পারে না। load job-এর অংশ হিসেবে গ্যাদার করলে বেশিরভাগ 'পরদিন সকালের' regression ঠেকে যায়।
📈 Plan regression আর 'গতকাল দ্রুত, আজ ধীর' তাড়া করছেন?
আমি optimizer ও statistics সমস্যা প্রমাণসহ নির্ণয় করি - গ্যাদারিং কৌশল, histogram, pending stats, আর plan স্থিতিশীলতা - যাতে আপনার plan উল্টানো বন্ধ হয়। বাংলাদেশ ও বিশ্বজুড়ে ক্লায়েন্ট।
তথ্যসূত্র ও আরও পড়ুন
- 📄 Oracle SQL Tuning Guide (19c) - Optimizer Statistics Concepts
- 📄 Oracle PL/SQL Packages Reference - DBMS_STATS
এই লেখার পদ্ধতি ও কেস স্টাডিগুলো ম্যানুফ্যাকচারিং, ব্যাংকিং ও ফার্মা পরিবেশে ১৮+ বছরের Oracle production ডেটাবেজ অ্যাডমিনিস্ট্রেশনের ভিত্তিতে লেখা।
