Android-এ SQLite-এর জন্য বিল্ট-ইন সহায়তা প্রদান করা হয়, যা একটি দক্ষ SQL ডেটাবেস। আপনার অ্যাপের পারফর্ম্যান্স অপ্টিমাইজ করতে এইসব পেশাদার পদ্ধতি অনুসরণ করুন, যাতে ডেটা বাড়ার সাথে সাথে এটি দ্রুত এবং অনুমানযোগ্যভাবে দ্রুত থাকে। এইসব পেশাদার পদ্ধতি ব্যবহার করে, আপনি পারফর্ম্যান্স সংক্রান্ত সমস্যা এড়ানোর সম্ভাবনাও কমাতে পারবেন, যেগুলি আবার তৈরি করা এবং সমাধান করা কঠিন।
আরও দ্রুত পারফর্ম্যান্স পেতে, এইসব পারফর্ম্যান্স নীতি অনুসরণ করুন:
কম সারি ও কলাম পড়ুন: শুধুমাত্র প্রয়োজনীয় ডেটা পেতে আপনার কোয়েরি অপ্টিমাইজ করুন। ডেটাবেস থেকে পড়া ডেটার পরিমাণ কমান, কারণ অতিরিক্ত ডেটা রিট্রিভ করলে পারফর্ম্যান্সের উপর প্রভাব পড়তে পারে।
SQLite ইঞ্জিনে কাজ পুশ করা: SQL কোয়েরির মধ্যে কম্পিউটেশন, ফিল্টারিং এবং সর্টিং অপারেশন করা। SQLite-এর কোয়েরি ইঞ্জিন ব্যবহার করলে পারফর্ম্যান্স উল্লেখযোগ্যভাবে উন্নত হতে পারে।
ডেটাবেস স্কিমা পরিবর্তন করা: SQLite-কে সাহায্য করার জন্য আপনার ডেটাবেস স্কিমা ডিজাইন করুন যাতে দক্ষ কোয়েরি প্ল্যান এবং ডেটা উপস্থাপনা তৈরি করা যায়। পারফর্ম্যান্স উন্নত করতে টেবিল সঠিকভাবে ইন্ডেক্স করুন এবং টেবিল স্ট্রাকচার অপ্টিমাইজ করুন।
এছাড়াও, আপনার SQLite ডেটাবেসের পারফর্ম্যান্স পরিমাপ করতে আপনি উপলভ্য সমস্যার সমাধানকারী টুল ব্যবহার করতে পারেন। এটি আপনাকে অপ্টিমাইজ করা প্রয়োজন এমন ক্ষেত্রগুলি শনাক্ত করতে সাহায্য করবে।
আমরা Jetpack Room লাইব্রেরি ব্যবহার করার সাজেশন দিই।
পারফর্ম্যান্সের জন্য ডেটাবেস কনফিগার করুন
SQLite-এ আপনার ডেটাবেসকে সর্বোত্তম পারফর্ম্যান্সের জন্য কনফিগার করতে এই বিভাগে উল্লেখ করা ধাপগুলি অনুসরণ করুন।
রাইট-অ্যাহেড লগিং চালু করুন
SQLite একটি লগে মিউটেশন যোগ করে তা প্রয়োগ করে, যা এটি মাঝে মাঝে ডেটাবেসে কম্প্যাক্ট করে। একে রাইট-অ্যাহেড লগিং (WAL) বলা হয়।
ATTACH DATABASE ব্যবহার না করলে
WAL চালু করুন।
PRAGMA journal_mode=WAL নিজে চালানোর পরিবর্তে ফ্রেমওয়ার্কের মাধ্যমে WAL চালু করুন। কম-RAM ডিভাইস নয় এমন ডিভাইসে Room ডিফল্ট হিসেবে WAL চালু করে।
Room ছাড়া, নিম্নলিখিতগুলির মধ্যে কোনও একটি ব্যবহার করুন:
enableWriteAheadLoggingপদ্ধতিENABLE_WRITE_AHEAD_LOGGINGওপেন ফ্ল্যাগSQLiteOpenHelper-এরsetWriteAheadLoggingEnabledপদ্ধতি
ফ্রেমওয়ার্ক যখন জানতে পারে যে ডেটাবেস WAL ব্যবহার করে, তখন এটি কানেকশনের একটি পুল খোলে যাতে রিড অপারেশন রাইট অপারেশনের সাথে একই সাথে চলতে পারে এবং এটি পরবর্তী বিভাগে বর্ণিত WAL সিঙ্ক্রোনাস মোড প্রয়োগ করে। আপনি নিজে
PRAGMA journal_mode=WAL চালালে, ফ্রেমওয়ার্ক এখনও ডেটাবেসকে
রোলব্যাক জার্নাল ডেটাবেস হিসেবে বিবেচনা করে। এটি একটি কানেকশন ব্যবহার করে এবং কমিট ডিফল্ট FULL সিনক্রোনাস মোড
চালু রাখে।
সিঙ্ক্রোনাইজেশন মোড রিল্যাক্স করুন
SQLite-এ সিঙ্ক্রোনাস মোড কন্ট্রোল করার বিকল্প আছে। FULL মোডে, ডেটা ডিস্কে পৌঁছেছে কিনা তা নিশ্চিত করতে প্রতিটি
কমিট একটি fsync ইস্যু করে। এটি
ডেটা কতটা টেকসই তা উন্নত করে কিন্তু আপনার কমিট স্লো করে দেয়। WAL-এর সাথে, NORMAL
মোড প্রতিটি কমিটে সিঙ্ক করে না এবং এখনও ডেটাবেসকে
দুর্নীতি থেকে রক্ষা করে।
ফ্রেমওয়ার্কের মাধ্যমে WAL চালু করলে, Android ডিভাইসের ডিফল্ট WAL সিঙ্ক্রোনাস মোড প্রয়োগ করে, যা Android 9 ও তার পরবর্তী ভার্সনের জন্য AOSP-তে NORMAL।
ডিভাইস প্রস্তুতকারক এই ডিফল্ট পরিবর্তন করতে পারেন এবং WAL ব্যবহার করে না এমন ডেটাবেস
ডিফল্ট হিসেবে FULL-এ সেট করা থাকে। আপনার ডেটাবেস যাতে NORMAL ব্যবহার করে তা নিশ্চিত করতে, ডেটাবেস খোলার সময়
এটি সেট করুন:
// When opening the database
val params = SQLiteDatabase.OpenParams.Builder()
.addOpenFlags(SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING)
// SYNC_MODE_NORMAL is available in API level 33 and higher.
// On API levels 28 to 32, pass "NORMAL" instead.
.setSynchronousMode(SQLiteDatabase.SYNC_MODE_NORMAL)
.build()
// With SQLiteOpenHelper, call setOpenParams(params) in your constructor.
// Or: after having opened the database
db.execSQL("PRAGMA synchronous = NORMAL")
এই সেটিংয়ে, ডিস্কে ডেটা স্টোর হওয়ার আগে একটি কমিট রিটার্ন করতে পারে। ডিভাইস বন্ধ হয়ে গেলে, যেমন পাওয়ার চলে যাওয়া বা কার্নেল প্যানিকের কারণে, কমিট করা ডেটা হারিয়ে যেতে পারে। তবে, লগিংয়ের কারণে আপনার ডেটাবেস খারাপ হয়নি।
শুধুমাত্র আপনার অ্যাপ ক্র্যাশ করলে, আপনার ডেটা ডিস্কে পৌঁছে যায়। বেশিরভাগ অ্যাপের ক্ষেত্রে, এই সেটিং কোনও খরচ ছাড়াই পারফর্ম্যান্স উন্নত করে।
কানেকশন ও মেমরি ম্যানেজ করা
প্রতিটি খোলা ডেটাবেস কানেকশন SQLite মেমরি ধরে রাখে: একটি পৃষ্ঠা ক্যাশে, একটি লুকাসাইড অ্যালোকেটর এবং কম্পাইল করা স্টেটমেন্টের একটি ক্যাশে। ডেটাবেস ফাইল পৃষ্ঠাগুলিও কার্নেল পৃষ্ঠা ক্যাশে থাকে। আপনার কোয়েরি স্লো না করে এই মেমরি চেক করতে এই বিভাগে উল্লেখ করা পদ্ধতি অনুসরণ করুন।
প্রতিটি ডেটাবেস একবার করে খুলুন
আপনার সম্পূর্ণ প্রসেসের জন্য প্রতিটি ডেটাবেস ফাইলে একটিই SQLiteOpenHelper বা RoomDatabase ইনস্ট্যান্স ব্যবহার করুন,
যেমন, এটিকে সিঙ্গলটন হিসেবে তৈরি করে। প্রতিটি ইনস্ট্যান্সের
নিজস্ব কানেকশন পুল, পৃষ্ঠা ক্যাশে ও স্টেটমেন্ট ক্যাশে থাকে এবং একই ফাইল খোলা
দুটি ইনস্ট্যান্স সেগুলি শেয়ার করতে পারে না। একটি বেঞ্চমার্কে, একই ফাইল খোলা দুটি হেল্পারকে একত্রিত করে একটি SQLite পৃষ্ঠা ক্যাশে মেমরি প্রায় অর্ধেক এবং p95 কোয়েরি লেটেন্সি অর্ধেকেরও বেশি কমিয়ে দেয়।
ডুপ্লিকেট ইনস্ট্যান্স খুঁজতে, একাধিক Connection pool for হেডিংয়ের অধীনে একই ডেটাবেস পাথের জন্য dumpsys dbinfo চেক করুন।
ওপেন কলব্যাক থেকে স্কিমা পরিবর্তন বাদ দিন
SQLiteOpenHelper-এর onOpen কলব্যাক প্রতিবার ডেটাবেস খোলার সময় রান করে এবং
RoomDatabase.Callback-এর onOpen কলব্যাকও তাই করে। CREATE TABLE IF NOT EXISTS এবং CREATE INDEX IF NOT EXISTS-এর মতো
স্টেটমেন্ট
onCreate এবং onUpgrade-এ অথবা রুম মাইগ্রেশনে রাখুন। onOpen-এ setVersion কল করবেন না, কারণ SQLiteOpenHelper ইতিমধ্যেই onCreate এবং onUpgrade-এর পরে
ভার্সন সেট করে। এমনকি স্কিমা আগে থেকেই থাকলেও, এইসব
স্টেটমেন্ট এখনও প্রতিবার খোলার সময় রান করে এবং setVersion ডেটাবেস
ফাইলে লেখে।
প্রস্তুত করা স্টেটমেন্ট ক্যাশের সাইজ নির্ধারণ করুন
Android প্রতিটি কানেকশনের জন্য কম্পাইল করা স্টেটমেন্ট ক্যাশে করে। ক্যাশে ডিফল্ট হিসেবে ২৫টি
স্টেটমেন্ট থাকে এবং আপনি setMaxSqlCacheSize-এর মাধ্যমে এটি বাড়িয়ে
MAX_SQL_CACHE_SIZE (১০০) করতে পারবেন। কোনও ডেটাবেস
তার হট পাথে ২৫টির বেশি স্বতন্ত্র স্টেটমেন্ট রান করলে, ক্যাশে থ্র্যাশ করে এবং
SQLite একই স্টেটমেন্ট বারবার কম্পাইল করে।
প্রথমে, আপনার কোয়েরি প্যারামিটারাইজ করুন। স্ট্রিং কনক্যাটেনেশন দিয়ে তৈরি SQL-এর ক্ষেত্রে কোনও ক্যাশে সাইজ
সাহায্য করতে পারে না, কারণ প্রতিটি স্ট্রিং হল একটি নতুন
স্টেটমেন্ট। তারপর dumpsys meminfo-এর POOL STATS-এ গিয়ে স্টেটমেন্ট ক্যাশে হিট ও মিস চেক করুন। যেমন, সেই বিভাগের স্যাম্পেল আউটপুটে phenotype.db ডেটাবেসে ১৪৭,৯২১টি হিট এবং ৮৯,৬১৬টি মিস আছে, যা ৩৮% মিস রেট। এই ধরনের ডেটাবেসে ক্যাশে সাইজ বাড়ালে
প্রায় সব মিস সরিয়ে দেওয়া যায়। প্রতিটি ক্যাশে করা স্টেটমেন্ট কয়েক কিলোবাইট মেমরি ব্যবহার করে।
ক্যাশের সাইজ বাড়াতে, আপনার SQLiteOpenHelper সাবক্লাসে onConfigure
ওভাররাইড করুন:
override fun onConfigure(db: SQLiteDatabase) {
db.setMaxSqlCacheSize(SQLiteDatabase.MAX_SQL_CACHE_SIZE)
}
আপনি Room ব্যবহার করলে, RoomDatabase.Callback-এর onOpen কলব্যাকে SupportSQLiteDatabase-এ একই পদ্ধতি কল করুন।
অ্যাক্টিভ নয় এমন ডেটাবেস বন্ধ করুন, তবে অ্যাক্টিভ ডেটাবেস খোলা রাখুন
একটি খোলা ডেটাবেস তার কানেকশন, ক্যাশে এবং ফাইল ডেসক্রিপটর রাখে, এমনকি যখন এটি নিষ্ক্রিয় থাকে। একটি বেঞ্চমার্কে, WAL মোডে একটি নিষ্ক্রিয় রুম ডেটাবেস যা খোলা ছিল SQLite পৃষ্ঠা ক্যাশে মেমরির প্রায় ৬৭৮ কেবি এবং ৫টি ফাইল ডেসক্রিপটর ধরে রেখেছে। আপনি যদি মাঝে মাঝে ডেটাবেস ব্যবহার করেন, যেমন দৈনিক সিঙ্ক করার সময়, তাহলে কাজ হয়ে গেলে এটি বন্ধ করে দিন।
নিষ্ক্রিয় ডেটাবেস নিরাপদে বন্ধ করতে, এইসব নির্দেশিকা অনুসরণ করুন:
- Room আপনার জন্য
setAutoCloseTimeout-এর মাধ্যমে একটি নিষ্ক্রিয় ডেটাবেস বন্ধ করতে পারে, যা একটি পরীক্ষামূলক API। পরের বার অ্যাক্সেস করার সময় Room ডেটাবেস আবার খোলে এবং আপনারonOpenকলব্যাক আবার রান করে, তাই যেকোনও কানেকশন কনফিগারেশন সেখানে রাখুন। SQLiteOpenHelper-এরsetIdleConnectionTimeoutটি পদ্ধতি এবংSQLiteDatabase.OpenParams.Builderএড়িয়ে চলুন। API লেভেল ২৯-এ এগুলি বন্ধ করে দেওয়া হয়েছে কারণPRAGMAস্টেটমেন্টের মাধ্যমে আপনি যেসব সেটিংস প্রয়োগ করেছেন সেগুলি রিস্টোর না করেই এগুলি কানেকশন বন্ধ করে দেয়।- আপনি প্রতি কয়েক সেকেন্ড অন্তর যে ডেটাবেসে কোয়েরি করেন সেটি বন্ধ করবেন না। আপনার কোয়েরির মধ্যেকার ব্যবধানের চেয়ে কম টাইমআউট হলে ডেটাবেস বারবার বন্ধ ও আবার খোলা হয়, যার ফলে প্রতিবার এর পৃষ্ঠা ক্যাশে ও স্টেটমেন্ট ক্যাশে বাতিল হয়ে যায় এবং পরের কোয়েরি আরও ধীরগতির হয়ে যায়।
SQLite মেমরির জন্য অ্যাকাউন্ট
SQLite মেমরি খুঁজে পাওয়া কঠিন হতে পারে। SQLite পৃষ্ঠা ক্যাশে এবং স্টেটমেন্ট ক্যাশে হল
নেটিভ হিপ অ্যালোকেশন। কিন্তু Android ডিফল্ট হিসেবে SQLite-এর মেমরি-ম্যাপ করা I/O চালু করে না, তাই SQLite সাধারণ ফাইল রিডের মাধ্যমে ডেটাবেস ফাইল পড়ে এবং কার্নেল ক্যাশে করা ফাইলের কন্টেন্ট হল আনম্যাপ করা ফাইল পৃষ্ঠা। dumpsys meminfo-এ আপনার অ্যাপের PSS-এ
এগুলি গণনা করা হয় না। cgroup v2 মেমরি অ্যাকাউন্টিং ব্যবহার করে এমন ডিভাইসে, কার্নেল এখনও আপনার অ্যাপের মেমরি cgroup-এ এইসব পৃষ্ঠা চার্জ করে। এই লুকানো ফুটপ্রিন্ট কমাতে, ইন্ডেক্স ব্যবহার করে কম পৃষ্ঠা পড়ুন এবং
শুধুমাত্র আপনার প্রয়োজনীয় সারি ও কলাম পড়ুন। আপনার প্রয়োজন নেই এমন ডেটাবেস বন্ধ করলে
সেগুলির SQLite ক্যাশেও খালি হয়ে যায়।
দক্ষ টেবিল স্কিমা সংজ্ঞায়িত করুন
পারফর্ম্যান্স অপ্টিমাইজ করতে এবং ডেটা খরচ কমাতে, একটি দক্ষ টেবিল স্কিমা সংজ্ঞায়িত করুন। SQLite কার্যকর কোয়েরি প্ল্যান ও ডেটা তৈরি করে, যার ফলে আরও দ্রুত ডেটা রিট্রিভ করা যায়। এই বিভাগে টেবিল স্কিমা তৈরি করার জন্য পেশাদার পদ্ধতি দেওয়া হয়েছে।
এই জিনিস খেয়াল রেখে সাজেশন INTEGER PRIMARY KEY
এই উদাহরণের জন্য, নিম্নলিখিতভাবে একটি টেবিল সংজ্ঞায়িত এবং পপুলেট করুন:
CREATE TABLE Customers(
id INTEGER,
name TEXT,
city TEXT
);
INSERT INTO Customers Values(456, 'John Lennon', 'Liverpool, England');
INSERT INTO Customers Values(123, 'Michael Jackson', 'Gary, IN');
INSERT INTO Customers Values(789, 'Dolly Parton', 'Sevier County, TN');
টেবিল আউটপুট নিচে দেওয়া হল:
| rowid | id | নাম | শহর |
|---|---|---|---|
| 1 | 456 | জন লেনন | লিভারপুল, ইংল্যান্ড |
| 2 | 123 | মাইকেল জ্যাকসন | গ্যারি, ইন্ডিয়ানা |
| 3 | 789 | ডলি পার্টন | সেভিয়ার কাউন্টি, টেনেসি (TN) |
rowid কলামটি হল
একটি ইন্ডেক্স যা ইনসার্ট করার ক্রম বজায় রাখে। rowid দ্বারা ফিল্টার করা কোয়েরিগুলি দ্রুত B-ট্রি সার্চ হিসেবে প্রয়োগ করা হয়, কিন্তু id দ্বারা ফিল্টার করা কোয়েরিগুলি একটি ধীর টেবিল স্ক্যান।
আপনি যদি id-এর মাধ্যমে লুকআপ করার পরিকল্পনা করেন, তাহলে স্টোরেজে কম ডেটা এবং সামগ্রিকভাবে দ্রুত ডেটাবেসের জন্য আপনি rowid কলাম স্টোর করা এড়াতে পারেন:
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
city TEXT
);
আপনার টেবিল এখন এইরকম দেখতে:
| id | নাম | শহর |
|---|---|---|
| 123 | মাইকেল জ্যাকসন | গ্যারি, ইন্ডিয়ানা |
| 456 | জন লেনন | লিভারপুল, ইংল্যান্ড |
| 789 | ডলি পার্টন | সেভিয়ার কাউন্টি, টেনেসি (TN) |
যেহেতু আপনাকে rowid কলাম স্টোর করতে হয় না, তাই id কোয়েরি দ্রুত হয়। মনে রাখবেন
যে টেবিলটি এখন ইনসার্ট করার ক্রমের পরিবর্তে id-এর ভিত্তিতে সাজানো হয়েছে।
ইনডেক্স ব্যবহার করে কোয়েরি দ্রুত করুন
SQLite কোয়েরি দ্রুত করতে
ইনডেক্স
ব্যবহার করে। কোনও কলাম ফিল্টার (WHERE), সর্ট (ORDER BY) বা
অ্যাগ্রিগেট (GROUP BY) করার সময়, টেবিলে কলামের জন্য কোনও ইন্ডেক্স থাকলে,
কোয়েরি দ্রুত হয়।
আগের উদাহরণে, city অনুযায়ী ফিল্টার করতে হলে পুরো টেবিল স্ক্যান করতে হবে:
SELECT id, name
WHERE city = 'London, England';
শহর সংক্রান্ত অনেক কোয়েরি আছে এমন অ্যাপের ক্ষেত্রে, আপনি একটি ইন্ডেক্স ব্যবহার করে সেই কোয়েরিগুলি দ্রুত করতে পারেন:
CREATE INDEX city_index ON Customers(city);
একটি ইন্ডেক্স অতিরিক্ত টেবিল হিসেবে প্রয়োগ করা হয়, ইন্ডেক্স কলাম অনুযায়ী সাজানো হয় এবং
rowid-এ ম্যাপ করা হয়:
| শহর | rowid |
|---|---|
| গ্যারি, ইন্ডিয়ানা | 2 |
| লিভারপুল, ইংল্যান্ড | 1 |
| সেভিয়ার কাউন্টি, টেনেসি (TN) | 3 |
মনে রাখবেন যে city কলামের স্টোরেজ খরচ এখন দ্বিগুণ, কারণ এটি
এখন আসল টেবিল এবং ইনডেক্স, দুটিতেই উপস্থিত আছে। আপনি
ইনডেক্স ব্যবহার করছেন বলে, অতিরিক্ত স্টোরেজের খরচ দ্রুত কোয়েরির সুবিধা পাওয়ার জন্য উপযুক্ত।
তবে, কোয়েরি পারফর্ম্যান্সের কোনও উন্নতি না হওয়ার জন্য স্টোরেজ খরচ দেওয়া এড়াতে, আপনি ব্যবহার করছেন না এমন কোনও ইন্ডেক্স বজায় রাখবেন না।
একাধিক কলামের ইন্ডেক্স তৈরি করুন
আপনার কোয়েরিতে একাধিক কলাম থাকলে, কোয়েরি সম্পূর্ণভাবে ত্বরান্বিত করতে আপনি মাল্টি-কলাম ইন্ডেক্স তৈরি করতে পারবেন। এছাড়াও, আপনি বাইরের কলামে ইন্ডেক্স ব্যবহার করতে পারেন এবং ভিতরের সার্চ লিনিয়ার স্ক্যান হিসেবে করতে দিতে পারেন।
যেমন, নিম্নলিখিত কোয়েরি দেওয়া হলে:
SELECT id, name
WHERE city = 'London, England'
ORDER BY city, name
কোয়েরিতে যেভাবে উল্লেখ করা হয়েছে সেই একই ক্রমে মাল্টি-কলাম ইন্ডেক্স ব্যবহার করে আপনি কোয়েরি দ্রুত করতে পারবেন:
CREATE INDEX city_name_index ON Customers(city, name);
তবে, আপনার কাছে শুধুমাত্র city-এ ইন্ডেক্স থাকলে, বাইরের অর্ডারিং এখনও
ত্বরান্বিত হয়, যখন ভিতরের অর্ডারিংয়ের জন্য একটি লিনিয়ার স্ক্যান প্রয়োজন।
এটি প্রিফিক্স ইনকোয়ারির ক্ষেত্রেও কাজ করে। যেমন, একটি ইন্ডেক্স
ON Customers (city, name) city-এর মাধ্যমে ফিল্টার করা, অর্ডার করা এবং গ্রুপ করার
প্রক্রিয়াকেও দ্রুত করে, কারণ মাল্টি-কলাম ইন্ডেক্সের জন্য ইন্ডেক্স টেবিলটি প্রদত্ত ক্রমে
প্রদত্ত ইন্ডেক্স দ্বারা অর্ডার করা হয়।
এই জিনিস খেয়াল রেখে সাজেশন WITHOUT ROWID
ডিফল্ট হিসেবে, SQLite আপনার টেবিলের জন্য একটি rowid কলাম তৈরি করে, যেখানে rowid হল একটি
ইমপ্লিসিট INTEGER PRIMARY KEY AUTOINCREMENT। আপনার কাছে আগে থেকেই INTEGER PRIMARY KEY
কলাম থাকলে, এই কলামটি rowid-এর অ্যালিয়াস হয়ে যাবে।
INTEGER বা কলামের কম্পোজিট ছাড়া অন্য প্রাথমিক কী আছে এমন টেবিলের জন্য,
WITHOUT ROWID বিবেচনা করুন।
ছোট ডেটা BLOB হিসেবে এবং বড় ডেটা ফাইল হিসেবে স্টোর করুন
আপনি যদি কোনও সারির সাথে বড় ডেটা যুক্ত করতে চান, যেমন কোনও ছবির থাম্বনেল
বা কোনও পরিচিতির ফটো, তাহলে আপনি ডেটা BLOB কলামে অথবা কোনও ফাইলে
স্টোর করতে পারেন এবং তারপর কলামে পাথ স্টোর করতে পারেন।
ফাইল সাধারণত ৪ কেবি ইনক্রিমেন্টে রাউন্ড আপ করা হয়। খুব ছোট ফাইলের ক্ষেত্রে, যেখানে
রাউন্ডিং এরর উল্লেখযোগ্য, সেখানে ডেটাবেসে
BLOB হিসেবে সেগুলি স্টোর করা আরও বেশি কার্যকর। SQLite ফাইল সিস্টেম কল কম করে এবং কিছু ক্ষেত্রে
আন্ডারলাইং ফাইল সিস্টেমের চেয়ে দ্রুত।
কোয়েরি পারফর্ম্যান্স উন্নত করুন
রেসপন্স টাইম কমিয়ে এবং প্রসেসিংয়ের দক্ষতা বাড়িয়ে SQLite-এ কোয়েরি পারফর্ম্যান্স উন্নত করতে এইসব পেশাদার পদ্ধতি অনুসরণ করুন।
আপনার প্রয়োজন শুধু সেই সারিগুলি পড়ুন
ফিল্টার আপনাকে নির্দিষ্ট মাপকাঠি উল্লেখ করে ফলাফল আরও নির্দিষ্ট করতে দেয়, যেমন তারিখের রেঞ্জ, লোকেশন বা নাম। সীমা আপনাকে ফলাফলের সংখ্যা নিয়ন্ত্রণ করতে দেয় যা আপনি দেখতে পান:
db.rawQuery("""
SELECT name
FROM Customers
LIMIT 10;
""".trimIndent(),
null
).use { cursor ->
while (cursor.moveToNext()) {
// Process cursor data
}
}
আপনার প্রয়োজন অনুযায়ী কলাম পড়ুন
অপ্রয়োজনীয় কলাম বেছে নেওয়া এড়িয়ে চলুন, কারণ এটি আপনার কোয়েরি স্লো করে দিতে পারে এবং রিসোর্স নষ্ট করতে পারে। পরিবর্তে, শুধুমাত্র ব্যবহার করা কলাম বেছে নিন।
নিচের উদাহরণে, আপনি id, name এবং phone বেছে নিয়েছেন:
// This is not the most efficient way of doing this.
// See the following example for a better approach.
db.rawQuery(
"""
SELECT id, name, phone
FROM customers;
""".trimIndent(),
null
).use { cursor ->
while (cursor.moveToNext()) {
val name = cursor.getString(1)
// Further processing
}
}
তবে, আপনার শুধু name কলামটি প্রয়োজন:
db.rawQuery("""
SELECT name
FROM Customers;
""".trimIndent(),
null
).use { cursor ->
while (cursor.moveToNext()) {
val name = cursor.getString(0)
// Further processing
}
}
কোয়েরি প্যারামিটারাইজ করা
আপনার কোয়েরি স্ট্রিংয়ে এমন প্যারামিটার থাকতে পারে যা শুধুমাত্র রানটাইমে জানা যায়, যেমন:
fun getNameById(id: Long): String?
db.rawQuery(
"SELECT name FROM customers WHERE id=$id", null
).use { cursor ->
return if (cursor.moveToFirst()) {
cursor.getString(0)
} else {
null
}
}
}
আগের কোডে, প্রতিটি কোয়েরি আলাদা স্ট্রিং তৈরি করে, তাই
স্টেটমেন্ট ক্যাশে থেকে কোনও সুবিধা পায় না। প্রতিটি কলের জন্য SQLite-কে এটি এক্সিকিউট করার আগে
কম্পাইল করতে হবে। পরিবর্তে, আপনি id আর্গুমেন্টকে একটি
প্যারামিটার দিয়ে প্রতিস্থাপন করতে পারেন এবং
selectionArgs-এর সাথে ভ্যালু বাইন্ড করতে পারেন:
fun getNameById(id: Long): String? {
db.rawQuery(
"""
SELECT name
FROM customers
WHERE id=?
""".trimIndent(), arrayOf(id.toString())
).use { cursor ->
return if (cursor.moveToFirst()) {
cursor.getString(0)
} else {
null
}
}
}
এখন কোয়েরি একবার কম্পাইল করে ক্যাশে করা যাবে। getNameById(long)-এর বিভিন্ন ইনভোকেশনের মধ্যে
কম্পাইল করা কোয়েরি আবার ব্যবহার করা হয়।
SQL-এ ইটারেট করুন, কোডে নয়
একটি মাত্র কোয়েরি ব্যবহার করুন যা সব টার্গেট করা ফলাফল দেখায়, SQL কোয়েরিতে প্রোগ্রাম্যাটিক লুপ ইটারেট করে আলাদা আলাদা ফলাফল দেখানোর পরিবর্তে। প্রোগ্রাম্যাটিক লুপ একটি SQL কোয়েরির থেকে প্রায় ১০০০ গুণ ধীরগতির।
অনন্য মানের জন্য DISTINCT ব্যবহার করুন
DISTINCT কীওয়ার্ড ব্যবহার করলে, প্রসেস করার জন্য প্রয়োজনীয় ডেটার পরিমাণ কমিয়ে
আপনার কোয়েরির পারফর্ম্যান্স উন্নত করা যেতে পারে। যেমন, আপনি যদি কোনও কলাম থেকে
শুধুমাত্র অনন্য মানগুলি ফেরত দিতে চান, তাহলে DISTINCT ব্যবহার করুন:
db.rawQuery("""
SELECT DISTINCT name
FROM Customers;
""".trimIndent(),
null
).use { cursor ->
while (cursor.moveToNext()) {
// Only iterate over distinct names in Kotlin
// Process distinct name
}
}
সম্ভব হলে এগ্রিগেট ফাংশন ব্যবহার করুন
সারি ডেটা ছাড়াই এগ্রিগেট ফলাফলের জন্য এগ্রিগেট ফাংশন ব্যবহার করুন। যেমন, নিম্নলিখিত কোড চেক করে দেখে যে অন্তত একটি ম্যাচিং সারি আছে কিনা:
// This is not the most efficient way of doing this.
// See the following example for a better approach.
db.rawQuery("""
SELECT id, name
FROM Customers
WHERE city = 'Paris';
""".trimIndent(),
null
).use { cursor ->
if (cursor.moveToFirst()) {
// At least one customer from Paris
// Handle found
} else {
// No customers from Paris
// Handle not found
}
শুধুমাত্র প্রথম সারিটি আনতে, আপনি EXISTS() ব্যবহার করতে পারেন যাতে কোনও ম্যাচিং সারি না থাকলে 0 এবং এক বা একাধিক সারি ম্যাচ করলে 1 রিটার্ন করা যায়:
db.rawQuery("""
SELECT EXISTS (
SELECT null
FROM Customers
WHERE city = 'Paris';
);
""".trimIndent(),
null
).use { cursor ->
if (cursor.moveToFirst() && cursor.getInt(0) == 1) {
// At least one customer from Paris
// Handle found
} else {
// No customers from Paris
// Handle not found
}
}
আপনার অ্যাপে SQLite এগ্রিগেট ফাংশন ব্যবহার করুন code:
COUNT: একটি কলামে কতগুলি সারি আছে তা গণনা করে।SUM: কলামের সব সংখ্যাগত মান যোগ করে।MINবাMAX: সর্বনিম্ন বা সর্বোচ্চ মান নির্ধারণ করে। সংখ্যাসূচক কলাম,DATEধরন এবং টেক্সট ধরনের জন্য কাজ করে।AVG: গড় সংখ্যাসূচক মান খুঁজে বের করে।GROUP_CONCAT: ঐচ্ছিক বিভাজক সহ স্ট্রিং কনক্যাটিনেট করে।
Cursor.getCount()-এর পরিবর্তে COUNT() ব্যবহার করুন
নিম্নলিখিত
উদাহরণে,
Cursor.getCount() ফাংশন
ডেটাবেস থেকে সব সারি পড়ে এবং সব সারির ভ্যালু দেখায়:
// This is not the most efficient way of doing this.
// See the following example for a better approach.
db.rawQuery("""
SELECT id
FROM Customers;
""".trimIndent(),
null
).use { cursor ->
val count = cursor.getCount()
// Use count
}
তবে, COUNT() ব্যবহার করে, ডেটাবেস শুধুমাত্র
সংখ্যাটি দেখায়:
db.rawQuery("""
SELECT COUNT(*)
FROM Customers;
""".trimIndent(),
null
).use { cursor ->
cursor.moveToFirst()
val count = cursor.getInt(0)
// Use count
}
কোডের পরিবর্তে নেস্ট কোয়েরি
SQL কম্পোজেবল এবং সাবকোয়েরি, জয়েন ও ফরেন কী কনস্ট্রেন্ট সাপোর্ট করে। অ্যাপ কোড ব্যবহার না করেই আপনি একটি কোয়েরির ফলাফল অন্য কোয়েরিতে ব্যবহার করতে পারবেন। এর ফলে SQLite থেকে ডেটা কপি করার প্রয়োজন কমে যায় এবং ডেটাবেস ইঞ্জিন আপনার কোয়েরি অপ্টিমাইজ করতে পারে।
নিচের উদাহরণে, কোন শহরে সবচেয়ে বেশি গ্রাহক আছেন তা খুঁজে পেতে আপনি একটি কোয়েরি চালাতে পারেন, তারপর সেই শহরের সব গ্রাহককে খুঁজে পেতে অন্য একটি কোয়েরিতে ফলাফল ব্যবহার করতে পারেন:
// This is not the most efficient way of doing this.
// See the following example for a better approach.
db.rawQuery("""
SELECT city
FROM Customers
GROUP BY city
ORDER BY COUNT(*) DESC
LIMIT 1;
""".trimIndent(),
null
).use { cursor ->
if (cursor.moveToFirst()) {
val topCity = cursor.getString(0)
db.rawQuery("""
SELECT name, city
FROM Customers
WHERE city = ?;
""".trimIndent(),
arrayOf(topCity)).use { innerCursor ->
while (innerCursor.moveToNext()) {
// Process inner cursor data
}
}
}
}
আগের উদাহরণের অর্ধেক সময়ে ফলাফল পেতে, নেস্টেড স্টেটমেন্ট সহ একটি SQL কোয়েরি ব্যবহার করুন:
db.rawQuery("""
SELECT name, city
FROM Customers
WHERE city IN (
SELECT city
FROM Customers
GROUP BY city
ORDER BY COUNT (*) DESC
LIMIT 1;
);
""".trimIndent(),
null
).use { cursor ->
if (cursor.moveToNext()) {
// Process cursor data
}
}
SQL-এ অনন্যতা চেক করা
টেবিলে কোনও নির্দিষ্ট কলামের ভ্যালু অনন্য না হলে যদি কোনও সারি যোগ করা না যায়, তাহলে কলামের বাধ্যবাধকতা হিসেবে সেই অনন্যতা এনফোর্স করা আরও কার্যকর হতে পারে।
নিম্নলিখিত উদাহরণে, যে সারি যোগ করা হবে সেটি যাচাই করার জন্য একটি কোয়েরি রান করা হয় এবং আরেকটি কোয়েরি রান করা হয় আসলে যোগ করার জন্য:
// This is not the most efficient way of doing this.
// See the following example for a better approach.
db.rawQuery(
"""
SELECT EXISTS (
SELECT null
FROM customers
WHERE username = ?
);
""".trimIndent(),
arrayOf(customer.username)
).use { cursor ->
if (cursor.moveToFirst() && cursor.getInt(0) == 1) {
throw AddCustomerException(customer)
}
}
db.execSQL(
"INSERT INTO customers VALUES (?, ?, ?)",
arrayOf(
customer.id.toString(),
customer.name,
customer.username
)
)
Kotlin-এ অনন্য কনস্ট্রেন্ট চেক করার পরিবর্তে, আপনি টেবিল সংজ্ঞায়িত করার সময় SQL-এ এটি চেক করতে পারেন:
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
username TEXT UNIQUE
);
SQLite নিম্নলিখিত কাজগুলি করে:
CREATE TABLE Customers(...);
CREATE UNIQUE INDEX CustomersUsername ON Customers(username);
এখন আপনি একটি সারি যোগ করতে এবং SQLite-কে কনস্ট্রেন্ট চেক করতে দিতে পারেন:
try {
db.execSQL(
"INSERT INTO Customers VALUES (?, ?, ?)",
arrayOf(customer.id.toString(), customer.name, customer.username)
)
} catch(e: SQLiteConstraintException) {
throw AddCustomerException(customer, e)
}
SQLite একাধিক কলাম সহ অনন্য ইন্ডেক্স সমর্থন করে:
CREATE TABLE table(...);
CREATE UNIQUE INDEX unique_table ON table(column1, column2, ...);
SQLite, Kotlin কোডের তুলনায় দ্রুত এবং কম ওভারহেড সহ সীমাবদ্ধতা যাচাই করে। অ্যাপ কোডের পরিবর্তে SQLite ব্যবহার করা সবচেয়ে ভালো পদ্ধতি।
একটি ট্রানজ্যাকশনে একাধিক ইনসার্শন ব্যাচ করুন
একটি ট্রানজ্যাকশন একাধিক অপারেশন করে, যা শুধু দক্ষতাই নয়, সঠিকতাও উন্নত করে। ডেটার ধারাবাহিকতা উন্নত করতে এবং পারফর্ম্যান্স আরও দ্রুত করতে, আপনি ব্যাচ ইনসার্ট করতে পারেন:
db.beginTransaction()
try {
customers.forEach { customer ->
db.execSQL(
"INSERT INTO Customers VALUES (?, ?, ?)",
arrayOf(customer.id.toString(), customer.name, "customerValue")
)
}
db.setTransactionSuccessful()
} finally {
db.endTransaction()
}
স্পষ্ট ট্রানজ্যাকশনের বাইরে, প্রতিটি রাইট তার নিজস্ব অন্তর্নিহিত ট্রানজ্যাকশনে চলে এবং FULL সিঙ্ক্রোনাস মোডে fsync সহ কমিটের সম্পূর্ণ খরচ প্রদান করে। ব্যাচিং সেই খরচ একবারই করে, যা লেখার একটি ব্যাচকে
একটি অর্ডার অফ ম্যাগনিটিউড বা তার চেয়ে বেশি দ্রুত করতে পারে।
সমস্যার সমাধানের জন্য টুল ব্যবহার করুন
পারফর্ম্যান্স পরিমাপ করতে সাহায্য করার জন্য SQLite নিম্নলিখিত সমস্যার সমাধানকরণ টুল প্রদান করে।
SQLite-এর ইন্টার্যাক্টিভ প্রম্পট ব্যবহার করুন
কোয়েরি রান করতে ও শিখতে আপনার মেশিনে SQLite রান করুন।
বিভিন্ন Android প্ল্যাটফর্ম ভার্সন SQLite-এর বিভিন্ন রিভিশন ব্যবহার করে। Android-চালিত ডিভাইসে
যে ইঞ্জিন আছে সেটি ব্যবহার করতে, adb shell ব্যবহার করুন এবং
আপনার টার্গেট ডিভাইসে sqlite3 রান করুন।
আপনি SQLite-কে কোয়েরির সময় নির্ধারণ করতে বলতে পারেন:
sqlite> .timer on
sqlite> SELECT ...
Run Time: real ... user ... sys ...
EXPLAIN QUERY PLAN
আপনি SQLite-কে জিজ্ঞাসা করতে পারেন যে এটি কীভাবে
EXPLAIN QUERY PLAN ব্যবহার করে কোনও কোয়েরির উত্তর দিতে চায়:
sqlite> EXPLAIN QUERY PLAN
SELECT id, name
FROM Customers
WHERE city = 'Paris';
QUERY PLAN
`--SCAN Customers
প্যারিসের সব গ্রাহককে খুঁজে পেতে আগের উদাহরণে ইন্ডেক্স ছাড়াই সম্পূর্ণ টেবিল স্ক্যান করতে হবে। একে লিনিয়ার জটিলতা বলা হয়। SQLite-কে সব সারি পড়তে হবে এবং শুধুমাত্র প্যারিসের গ্রাহকদের সাথে মিলে যাওয়া সারিগুলি রাখতে হবে। এটি সমাধান করতে, আপনি একটি ইন্ডেক্স যোগ করতে পারেন:
sqlite> CREATE INDEX Idx1 ON Customers(city);
sqlite> EXPLAIN QUERY PLAN
SELECT id, name
FROM Customers
WHERE city = 'Paris';
QUERY PLAN
`--SEARCH test USING INDEX Idx1 (city=?
আপনি ইন্টার্যাক্টিভ শেল ব্যবহার করলে, SQLite-কে সবসময় কোয়েরি প্ল্যান ব্যাখ্যা করতে বলতে পারেন:
sqlite> .eqp on
আরও তথ্যের জন্য, কোয়েরি প্ল্যানিং দেখুন।
SQLite Analyzer
SQLite sqlite3_analyzer কমান্ড-লাইন ইন্টারফেস (CLI) অফার করে যাতে
পারফর্ম্যান্স সংক্রান্ত সমস্যার সমাধান করার জন্য অতিরিক্ত তথ্য ডাম্প করা যায়। ইনস্টল করতে,
SQLite ডাউনলোড পৃষ্ঠা দেখুন।
বিশ্লেষণের জন্য টার্গেট ডিভাইস থেকে আপনার ওয়ার্কস্টেশনে ডেটাবেস ফাইল ডাউনলোড করতে আপনি adb pull ব্যবহার করতে পারেন:
adb pull /data/data/<app_package_name>/databases/<db_name>.db
SQLite Browser
এছাড়াও, আপনি SQLite ডাউনলোড পৃষ্ঠা থেকে GUI টুল SQLite Browser ইনস্টল করতে পারবেন।
Android লগিং
Android, SQLite কোয়েরির সময় নির্ধারণ করে এবং আপনার জন্য সেগুলি লগ করে:
# Enable query time logging
$ adb shell setprop log.tag.SQLiteTime VERBOSE
# Disable query time logging
$ adb shell setprop log.tag.SQLiteTime ERROR
Android 13 ও তার পরবর্তী যেকোনও ভার্সনে, আপনি শুধু ধীরগতির অপারেশনও লগ করতে পারবেন। মিলিসেকেন্ডে থ্রেশহোল্ড সেট করুন, তারপর আপনার অ্যাপ রিস্টার্ট করুন যাতে এটি লগ ট্যাগ পড়তে পারে:
$ adb shell setprop log.tag.SQLiteSlowQueries VERBOSE
$ adb shell setprop db.log.slow_query_threshold 100
$ adb shell am force-stop <package-name>
ধীরগতির অপারেশনগুলি logcat-এ SQLiteConnection ট্যাগের অধীনে তাদের
সময়কাল, SQL এবং ডেটাবেস পাথ সহ প্রদর্শিত হয়। একটি অ্যাপের জন্য থ্রেশহোল্ড সেট করতে,
db.log.slow_query_threshold.<uid> ব্যবহার করুন, যেখানে <uid> হল অ্যাপের UID।
Perfetto ট্রেসিং
Perfetto কনফিগার করার সময়, ট্রেসে SQLite অপারেশন অন্তর্ভুক্ত করতে
database atrace বিভাগ যোগ করুন। SQL রান করার সময় প্রতিটি থ্রেড কী করছে তা দেখতে, শিডিউলার ইভেন্টও যোগ করুন:
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_waking"
ftrace_events: "sched/sched_blocked_reason"
atrace_categories: "database"
}
}
}
Android প্রতিটি ডেটাবেস অপারেশনকে অপারেশনের নাম এবং এর SQL-এর নামানুসারে একটি স্লাইস হিসেবে ট্রেস করে, যেমন
executeForCursorWindow SELECT * FROM message WHERE body LIKE ?। SQL তার ?টি প্লেসহোল্ডার
রাখে এবং ২৫৬টি অক্ষরে ট্রাঙ্কেট করা হয়। বাইন্ড আর্গুমেন্ট
অন্তর্ভুক্ত করা হয়নি। অপারেশনের মধ্যে open, close, prepare, execute এবং
executeFor ভ্যারিয়েন্ট অন্তর্ভুক্ত। prepare স্লাইস থাকার অর্থ এই নয় যে স্টেটমেন্ট ক্যাশে
মিস হয়েছে।
ট্র্যাক লেআউট Android ভার্সনের উপর নির্ভর করে:
- Android 17 ও এর পরবর্তী ভার্সনে, প্রতিটি কানেকশনের অ্যাপ প্রসেসে একটি অ্যাসিঙ্ক ট্র্যাক থাকে
যার নাম
SQLiteConnection-এবং এর পরে একটি কানেকশন নম্বর থাকে। ট্রানজ্যাকশনTRANSACTION-EXCLUSIVE,TRANSACTION-IMMEDIATEবাTRANSACTION-DEFERREDস্লাইস হিসেবে দেখা যায়, যে স্টেটমেন্টগুলি এতে রান করেছে সেগুলি নিচে নেস্ট করা থাকে। যে থ্রেড কোনও অপারেশন চালায়, সেটিও প্রতিটি ট্রানজ্যাকশন এবং ট্রানজ্যাকশনের বাইরে প্রতিটি স্টেটমেন্টের জন্য তার থ্রেড ট্র্যাকেSTART-এবংEND-ইনস্ট্যান্ট ইভেন্ট পায়। এইসব ইভেন্টের নাম কানেকশন ট্র্যাকের নাম দিয়ে শেষ হয়, যেমন-SQLiteConnection-0। - Android 16 ও তার আগের ভার্সনে, প্রতিটি স্বতন্ত্র SQL স্টেটমেন্টের অ্যাপ প্রসেসে নিজস্ব অ্যাসিঙ্ক ট্র্যাক থাকে এবং ট্রানজ্যাকশন ট্র্যাক করা হয় না।
প্রতিটি ডেটাবেসের জন্য কানেকশন নম্বর 0 থেকে শুরু হয়, তাই কোনও প্রসেসে সব ডেটাবেসের প্রাইমারি কানেকশন
SQLiteConnection-0 ট্র্যাক শেয়ার করে। আপনার অ্যাপ একই সময়ে একাধিক ডেটাবেস ব্যবহার করলে, থ্রেড ট্র্যাকে START- এবং END- ইভেন্টের মাধ্যমে সময়সীমা পরিমাপ করুন।
ছবি ১. Android 17-এ মূল থ্রেডে সম্পূর্ণ টেবিল স্ক্যান।
আপনি Perfetto UI-তে PerfettoSQL বা
trace_processor-এর মাধ্যমে এইসব স্লাইস বিশ্লেষণ করতে পারবেন। নিম্নলিখিত কোয়েরি প্রতিটি প্রসেসে সবচেয়ে ঘন ঘন SQLite
অপারেশন খুঁজে বের করে। এই কোয়েরি সব Android ভার্সনে কাজ করে:
INCLUDE PERFETTO MODULE slices.with_context;
SELECT process_name, name AS operation, COUNT(*) AS count
FROM process_slice
WHERE name GLOB 'open *' OR name GLOB 'close *'
OR name GLOB 'prepare *' OR name GLOB 'execute*'
OR name GLOB 'TRANSACTION-*'
GROUP BY process_name, name
ORDER BY count DESC
LIMIT 10;
নিম্নলিখিত কোয়েরি SQLite অপারেশন তালিকাভুক্ত করে যা Android 17 ও এর পরের যেকোনও ভার্সনে মেন থ্রেডে ১ মিলিসেকেন্ডের বেশি সময় নিয়েছে:
INCLUDE PERFETTO MODULE slices.with_context;
WITH marks AS (
SELECT process_name, utid, is_main_thread, ts, name,
LEAD(ts) OVER (PARTITION BY utid ORDER BY ts) AS end_ts,
LEAD(name) OVER (PARTITION BY utid ORDER BY ts) AS end_name
FROM thread_slice
WHERE name GLOB 'START-*' OR name GLOB 'END-*'
)
SELECT process_name, ts, ROUND((end_ts - ts) / 1e6, 1) AS dur_ms,
substr(name, 7) AS operation
FROM marks
WHERE name GLOB 'START-*'
AND end_name = 'END-' || substr(name, 7)
AND is_main_thread
AND end_ts - ts > 1e6
ORDER BY dur_ms DESC
LIMIT 10;
ছবি ২. মূল থ্রেডে SQLite অপারেশন ১ মিলি সেকেন্ডের বেশি সময় ধরে চলছে।
নিম্নলিখিত কোয়েরি এমন থ্রেড খুঁজে বের করে যা একটি এক্সপ্লিসিট ট্রানজ্যাকশনের বাইরে লেখে, যেখানে প্রতিটি লেখা তার নিজস্ব কমিটের জন্য অর্থ প্রদান করে, Android 17 এবং তার পরবর্তী ভার্সনে:
INCLUDE PERFETTO MODULE slices.with_context;
WITH ops AS (
SELECT process_name, thread_name, utid, name,
upper(ltrim(substr(name, instr(name, ' ') + 1))) AS sql
FROM thread_slice
WHERE name GLOB 'START-*'
)
SELECT process_name, thread_name,
SUM(name GLOB 'START-TRANSACTION-*') AS transactions,
SUM(name GLOB 'START-execute*' AND (sql GLOB 'INSERT *'
OR sql GLOB 'UPDATE *' OR sql GLOB 'DELETE *' OR sql GLOB 'REPLACE *'))
AS autocommit_writes
FROM ops
GROUP BY utid
HAVING autocommit_writes > 0
ORDER BY autocommit_writes DESC
LIMIT 10;
dumpsys meminfo
adb shell dumpsys meminfo <package-name> অ্যাপের মেমরি ব্যবহার সম্পর্কিত পরিসংখ্যান প্রিন্ট করে, যার মধ্যে DATABASES বিভাগে SQLite সম্পর্কে বিবরণ অন্তর্ভুক্ত রয়েছে। যেমন, এটি ডেভেলপারের ডিভাইসে
adb shell dumpsys meminfo com.google.android.gms.persistent-এর আউটপুট থেকে
নেওয়া হয়েছে:
DATABASES
pgsz dbsz Lookaside(b) cache hits cache misses cache size Dbname
PER CONNECTION STATS
4 52 45 8 41 6 /data/user/10/com.google.android.gms/databases/gaia-discovery
4 8 0 0 0 (attached) temp
4 52 56 5 23 6 /data/user/10/com.google.android.gms/databases/gaia-discovery (1)
4 252 95 233 124 12 /data/user_de/10/com.google.android.gms/databases/phenotype.db
4 8 0 0 0 (attached) temp
4 252 17 0 17 1 /data/user_de/10/com.google.android.gms/databases/phenotype.db (1)
4 9280 105 103169 69805 25 /data/user/10/com.google.android.gms/databases/phenotype.db
4 20 0 0 0 (attached) temp
4 9280 108 13877 6394 25 /data/user/10/com.google.android.gms/databases/phenotype.db (2)
4 8 0 0 0 (attached) temp
4 9280 105 12548 5519 25 /data/user/10/com.google.android.gms/databases/phenotype.db (3)
4 8 0 0 0 (attached) temp
4 9280 107 18328 7886 25 /data/user/10/com.google.android.gms/databases/phenotype.db (1)
4 8 0 0 0 (attached) temp
4 36 51 156 29 5 /data/user/10/com.google.android.gms/databases/mobstore_gc_db_v0
4 36 97 47 27 10 /data/user/10/com.google.android.gms/databases/context_feature_default.db
4 36 56 3 16 4 /data/user/10/com.google.android.gms/databases/context_feature_default.db (2)
4 300 40 2111 24 5 /data/user/10/com.google.android.gms/databases/gservices.db
4 300 39 3 17 4 /data/user/10/com.google.android.gms/databases/gservices.db (1)
4 20 17 0 14 1 /data/user/10/com.google.android.gms/databases/gms.notifications.db
4 20 33 1 15 2 /data/user/10/com.google.android.gms/databases/gms.notifications.db (1)
4 120 40 143 163 4 /data/user/10/com.google.android.gms/databases/android_pay
4 120 123 86 32 19 /data/user/10/com.google.android.gms/databases/android_pay (1)
4 28 33 4 17 3 /data/user/10/com.google.android.gms/databases/googlesettings.db
POOL STATS
cache hits cache misses cache size Dbname
13 68 81 /data/user/10/com.google.android.gms/databases/gaia-discovery
233 145 378 /data/user_de/10/com.google.android.gms/databases/phenotype.db
147921 89616 237537 /data/user/10/com.google.android.gms/databases/phenotype.db
156 30 186 /data/user/10/com.google.android.gms/databases/mobstore_gc_db_v0
50 57 107 /data/user/10/com.google.android.gms/databases/context_feature_default.db
2114 43 2157 /data/user/10/com.google.android.gms/databases/gservices.db
1 31 32 /data/user/10/com.google.android.gms/databases/gms.notifications.db
229 197 426 /data/user/10/com.google.android.gms/databases/android_pay
4 18 22 /data/user/10/com.google.android.gms/databases/googlesettings.db
Android 14 এবং তার পরবর্তী যেকোনও ভার্সনে উদাহরণে দেখানো PER CONNECTION STATS এবং POOL STATS টেবিল
প্রিন্ট করুন। Android 13 এবং তার আগের ভার্সন শুধুমাত্র প্রতি-কানেকশন টেবিল প্রিন্ট করে,
যেখানে ক্যাশে পরিসংখ্যান একটি একক cache কলামে
hits/misses/size হিসেবে একত্রিত করা হয়।
PER CONNECTION STATS-এর অধীনে, আপনি এগুলি দেখতে পাবেন:
pgsz: একটি ডেটাবেস পৃষ্ঠার সাইজ, কেবিতে।dbsz: কেবিতে, সম্পূর্ণ ডেটাবেস ফাইলের সাইজ।Lookaside(b): কানেকশন ব্যবহার করে এমন লুকাসাইড মেমরি স্লট-এর সংখ্যা। হেডারে(b)থাকলেও, এটি স্লটের সংখ্যা, বাইট নয়।cache hits,cache missesএবংcache size: কানেকশনের প্রস্তুত করা স্টেটমেন্ট ক্যাশের পরিসংখ্যান। এইসব ভ্যালু কম্পাইল করা SQL স্টেটমেন্ট গণনা করে, ডেটাবেস পৃষ্ঠা নয়।Dbname: ডেটাবেস ফাইলের পাথ। একই ডেটাবেসে অতিরিক্ত কানেকশনের ক্ষেত্রে বন্ধনীতে একটি নম্বর যোগ করা থাকে, যেমন(1)। WAL-এর মাধ্যমে, একটি ডেটাবেসে একাধিক কানেকশন থাকতে পারে যাতে রিড সমান্তরালভাবে চলতে পারে।
POOL STATS-এর অধীনে, আপনি প্রতিটি কানেকশন পুলের জন্য প্রস্তুত করা স্টেটমেন্ট ক্যাশে পরিসংখ্যান পাবেন:
cache hits: SQLite প্রস্তুত করা স্টেটমেন্ট ক্যাশে করে এবং কোয়েরি চালানোর সময় সেগুলি আবার ব্যবহার করার চেষ্টা করে, যাতে SQL স্টেটমেন্ট কম্পাইল করার সময় কিছুটা পরিশ্রম ও মেমরি বাঁচানো যায়। এটি হল স্টেটমেন্ট ক্যাশে হিটের সংখ্যা।cache misses: স্টেটমেন্ট ক্যাশে মিসের সংখ্যা।cache size: Android 16 QPR2 ও তার পরবর্তী ভার্সনে, ক্যাশেতে প্রস্তুত করা স্টেটমেন্টের সংখ্যা, পুলের নিষ্ক্রিয় কানেকশন জুড়ে যোগ করা হয়েছে। Android 14 থেকে Android 16 QPR1 পর্যন্ত, এই ভ্যালু হল হিট ও মিসের যোগফল এবং এটি ক্যাশের সাইজ দেখায় না।
কী ঠিক করতে হবে তা নির্ধারণ করতে এই পরিসংখ্যান ব্যবহার করুন:
- মিস রেট বেশি হওয়ার অর্থ হল SQLite একই স্টেটমেন্ট বারবার কম্পাইল করে।
প্রথমে, স্ট্রিং কনক্যাটেনেশন দিয়ে তৈরি SQL খুঁজুন এবং
সেটিকে প্যারামিটারাইজ করুন। আপনার কোয়েরি আগে থেকেই
প্যারামিটারাইজ করা থাকলে এবং কানেকশন প্রতি
cache size২৫-এর সীমায় থাকলে, ক্যাশের সাইজ বাড়ান। - কোনও ডেটাবেস পাথ
(1)-স্টাইলের সাফিক্স ছাড়া একাধিকবার দেখা গেলে তার অর্থ হল আপনার অ্যাপ একই ফাইল দুবার খুলেছে। প্রতিটি ডেটাবেস একবার খুলুন। - আপনার অ্যাপ খুব কম ব্যবহার করে এমন একটি ডেটাবেস খোলা থাকাকালীন এখনও মেমরি ধরে রাখে। আপনার কাজ হয়ে গেলে এটি বন্ধ করে দিন।
dumpsys dbinfo
adb shell dumpsys dbinfo <package-name> আপনার অ্যাপের প্রতিটি কানেকশন পুলের স্ট্যাটাস প্রিন্ট করে। প্রতিটি কানেকশনের প্রস্তুত করা স্টেটমেন্ট ক্যাশেতে স্টেটমেন্টের তালিকাও তৈরি করতে -v যোগ করুন। Android 17-এর আউটপুটের উদাহরণ নিচে দেওয়া হল, যেখানে পাথ ও দীর্ঘ লাইন ছোট করা হয়েছে:
** Database info for pid 2705 [com.example.app] **
Connection pool for /data/user/0/com.example.app/databases/settings.db:
Open: true
Max connections: 4
Total execution time (ms): 4963
Total statements executed: 231239
Average time per statement (ms): 0
Configuration: openFlags=805306368, ..., journalMode=WAL, syncMode=NORMAL
IsReadOnlyDatabase: false
Available primary connection:
<none>
Available non-primary connections:
<none>
Acquired connections:
<none>
Connection waiters:
<none>
Connection pool for /data/user/0/com.example.app/databases/events.db:
Open: true
Max connections: 1
Total execution time (ms): 842
Total statements executed: 840
Average time per statement (ms): 1
Configuration: openFlags=268435456, ..., journalMode=TRUNCATE, syncMode=FULL
IsReadOnlyDatabase: false
Available primary connection:
Connection #0:
isPrimaryConnection: true
onlyAllowReadOnlyOperations: false
totalLongOperations: 0
Most recently executed operations:
0: [10-26 04:22:32.133] executeForChangedRowCount took 0ms -
succeeded, sql="DELETE FROM params WHERE app_id=?", path=...
1: [10-26 04:22:32.133] prepare took 0ms - succeeded,
sql="DELETE FROM params WHERE app_id=?", path=...
...
নিম্নলিখিত বিষয়গুলি দেখুন:
Configuration: পুলের জার্নাল মোড ও সিঙ্ক্রোনাস মোড। Android 13 ও তার পরবর্তী ভার্সনে, এই লাইনে ফ্রেমওয়ার্কের মাধ্যমে প্রয়োগ করা মোড দেখানো হয়, যার মধ্যে প্ল্যাটফর্ম ডিফল্টও অন্তর্ভুক্ত। উদাহরণে,events.dbFULLসিঙ্ক্রোনাস মোডের সাথে রোলব্যাক জার্নাল ব্যবহার করে, তাই প্রতিটি কমিট সম্পূর্ণ সিঙ্কের জন্য অপেক্ষা করে। এই লাইনে আপনার নিজের চালানোPRAGMAস্টেটমেন্ট দেখানো হয় না।Max connections: রাইটেবল ডেটাবেসের জন্য ১ ভ্যালু থাকার অর্থ হল ফ্রেমওয়ার্ক ডেটাবেসকে WAL মোডে আছে বলে বিবেচনা করে না, তাই রিড ও রাইট একটি কানেকশন শেয়ার করে।Total execution time,Total statements executedএবংAverage time per statement: Android 13 ও তার পরবর্তী যেকোনও ভার্সনে উপলভ্য। সবচেয়ে ব্যস্ত ডেটাবেস খুঁজতে এগুলি ব্যবহার করুন।Most recently executed operations: প্রতিটি কানেকশনে শেষ ২০টি অপারেশন, সেগুলির সময়সীমা এবং SQL। একই কোয়েরি লুপে রান করা বা ট্রানজ্যাকশনের বাইরে সারিতে অনেকবার লেখার মতো প্যাটার্ন খুঁজুন। Android 15 QPR1 এবং এর পরবর্তী ভার্সনে, এই তালিকায় এক্সপ্লিসিট ট্রানজ্যাকশনও অন্তর্ভুক্ত থাকে।totalLongOperationsএবংOperations exceeding 2000ms: Android 15 QPR1 এবং তার পরবর্তী ভার্সনে উপলভ্য। কতগুলি অপারেশন ও ট্রানজ্যাকশন সম্পূর্ণ হতে ২ সেকেন্ডের বেশি সময় লেগেছে, তা এখানে দেখানো হয় এবং শেষ ১০টি অপারেশন ও ট্রানজ্যাকশনের তালিকাও দেখানো হয়।Acquired connectionsএবংConnection waiters: কানেকশন যা ব্যবহার করা হচ্ছে এবং থ্রেড যা কানেকশনের জন্য অপেক্ষা করছে। ওয়েটাররা কন্টেনশন নির্দেশ করে, যেমন একটি দীর্ঘ রাইট ট্রানজ্যাকশন যা অন্যান্য রাইটকে ব্লক করে।Prepared statement cache:-v-এর মাধ্যমে প্রিন্ট করা হয়েছে। অনেক এন্ট্রি যা শুধুমাত্র আক্ষরিক মানে আলাদা, সেগুলি স্ট্রিং কনক্যাটেনেশনের মাধ্যমে তৈরি SQL-কে নির্দেশ করে।- একাধিক
Connection pool forহেডিংয়ের অধীনে একই পাথ থাকার অর্থ হল আপনার অ্যাপ একই ফাইল দু'বার খুলেছে।
অতিরিক্ত রিসোর্স
কন্টেন্ট দেখে
আপনার জন্য সাজেস্ট করা হয়েছে
- মনে রাখবেন: জাভাস্ক্রিপ্ট বন্ধ থাকলে লিঙ্ক টেক্সট দেখানো হয়
- কন্টিনিউয়াস ইন্টিগ্রেশনে বেঞ্চমার্ক রান করা
- ফ্রোজেন ফ্রেম
- Macrobenchmark ছাড়াই বেসলাইন প্রোফাইল তৈরি ও পরিমাপ করা