بهترین روش ها برای عملکرد SQLite

اندروید پشتیبانی داخلی از SQLite ، یک پایگاه داده کارآمد SQL ارائه می‌کند. این بهترین شیوه‌ها را برای بهینه‌سازی عملکرد برنامه‌تان دنبال کنید، و مطمئن شوید که با رشد داده‌هایتان، سریع و قابل پیش‌بینی سریع باقی می‌ماند. با استفاده از این بهترین شیوه‌ها، احتمال مواجهه با مشکلات عملکردی که بازتولید و عیب‌یابی آن‌ها دشوار است را نیز کاهش می‌دهید.

برای دستیابی به عملکرد سریعتر، این اصول عملکرد را دنبال کنید:

  • خواندن ردیف‌ها و ستون‌های کمتر : درخواست‌های خود را برای بازیابی فقط داده‌های ضروری بهینه کنید. مقدار داده های خوانده شده از پایگاه داده را به حداقل برسانید، زیرا بازیابی اطلاعات اضافی می تواند بر عملکرد تأثیر بگذارد.

  • کار را به موتور SQLite فشار دهید : محاسبات، فیلتر کردن، و عملیات مرتب سازی را در پرس و جوهای SQL انجام دهید. استفاده از موتور جستجوی SQLite می تواند عملکرد را به میزان قابل توجهی بهبود بخشد.

  • اصلاح طرح پایگاه داده : طرح پایگاه داده خود را طراحی کنید تا به SQLite کمک کند تا طرح های جستجوی کارآمد و نمایش داده ها را بسازد. جداول را به درستی فهرست بندی کنید و ساختار جدول را بهینه کنید تا عملکرد را افزایش دهید.

علاوه بر این، می توانید از ابزارهای عیب یابی موجود برای اندازه گیری عملکرد پایگاه داده SQLite خود برای کمک به شناسایی مناطقی که نیاز به بهینه سازی دارند استفاده کنید.

توصیه می کنیم از کتابخانه اتاق جت پک استفاده کنید.

پایگاه داده را برای عملکرد پیکربندی کنید

مراحل این بخش را دنبال کنید تا پایگاه داده خود را برای عملکرد بهینه در SQLite پیکربندی کنید.

ثبت پیش از نوشتن را فعال کنید

SQLite جهش‌ها را با اضافه کردن آنها به یک گزارش پیاده‌سازی می‌کند، که گاهی اوقات آن را در پایگاه داده فشرده می‌کند. به این حالت ثبت پیش از نوشتن (WAL) می گویند.

WAL را فعال کنید مگر اینکه از ATTACH DATABASE استفاده کنید.

حالت همگام سازی را آرام کنید

هنگام استفاده از WAL، به طور پیش‌فرض هر commit یک fsync صادر می‌کند تا اطمینان حاصل شود که داده‌ها به دیسک می‌رسند. این دوام داده ها را بهبود می بخشد اما تعهدات شما را کند می کند.

SQLite گزینه ای برای کنترل حالت همزمان دارد. اگر WAL را فعال کنید، حالت همزمان را روی NORMAL تنظیم کنید:

کاتلین

db.execSQL("PRAGMA synchronous = NORMAL")

جاوا

db.execSQL("PRAGMA synchronous = NORMAL");

در این تنظیمات، یک commit می تواند قبل از ذخیره داده ها در یک دیسک بازگردد. اگر خاموشی دستگاه رخ دهد، مانند قطع برق یا وحشت هسته، ممکن است داده های تعهد شده از بین بروند. با این حال، به دلیل ورود به سیستم، پایگاه داده شما خراب نیست.

اگر فقط برنامه شما خراب شود، اطلاعات شما همچنان به دیسک می رسد. برای اکثر برنامه‌ها، این تنظیم بدون هیچ هزینه‌ای باعث بهبود عملکرد می‌شود.

طرحواره های جدول کارآمد را تعریف کنید

برای بهینه سازی عملکرد و به حداقل رساندن مصرف داده، یک جدول جدول کارآمد تعریف کنید. 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');

خروجی جدول به شرح زیر است:

ردیف شناسه نام شهر
1 456 جان لنون لیورپول، انگلستان
2 123 مایکل جکسون گری، IN
3 789 دالی پارتون شهرستان سویر، TN

rowid ستون شاخصی است که ترتیب درج را حفظ می کند. پرس و جوهایی که بر اساس rowid فیلتر می شوند به عنوان یک جستجوی سریع B-tree پیاده سازی می شوند، اما پرس و جوهایی که بر اساس id فیلتر می شوند، اسکن جدول آهسته هستند.

اگر قصد دارید جستجوها را با id انجام دهید، می‌توانید از ذخیره ستون rowid برای داده‌های کمتر در فضای ذخیره‌سازی و به طور کلی پایگاه داده سریع‌تر خودداری کنید:

CREATE TABLE Customers(
  id INTEGER PRIMARY KEY,
  name TEXT,
  city TEXT
);

جدول شما اکنون به صورت زیر است:

شناسه نام شهر
123 مایکل جکسون گری، IN
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 نگاشت می شود:

شهر ردیف
گری، IN 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 یا در یک فایل ذخیره کنید و سپس مسیر فایل را در ستون ذخیره کنید.

فایل ها به طور کلی تا 4 کیلوبایت افزایش می یابند. برای فایل‌های بسیار کوچک، جایی که خطای گرد کردن قابل توجه است، ذخیره آن‌ها در پایگاه داده به صورت BLOB کارآمدتر است. SQLite تماس های سیستم فایل را به حداقل می رساند و در برخی موارد سریعتر از فایل سیستم زیرین است.

بهبود عملکرد پرس و جو

این بهترین شیوه ها را برای بهبود عملکرد پرس و جو در SQLite با به حداقل رساندن زمان پاسخ و به حداکثر رساندن کارایی پردازش دنبال کنید.

فقط ردیف هایی را که نیاز دارید بخوانید

فیلترها به شما امکان می دهند با تعیین معیارهای خاصی مانند محدوده تاریخ، مکان یا نام، نتایج خود را محدود کنید. محدودیت‌ها به شما امکان می‌دهند تعداد نتایجی را که می‌بینید کنترل کنید:

کاتلین

db.rawQuery("""
    SELECT name
    FROM Customers
    LIMIT 10;
    """.trimIndent(),
    null
).use { cursor ->
    while (cursor.moveToNext()) {
        ...
    }
}

جاوا

try (Cursor cursor = db.rawQuery("""
    SELECT name
    FROM Customers
    LIMIT 10;
    """, null)) {
  while (cursor.moveToNext()) {
    ...
  }
}

فقط ستون هایی را که نیاز دارید بخوانید

از انتخاب ستون‌های غیرضروری خودداری کنید، که می‌تواند درخواست‌های شما را کند کند و منابع را هدر دهد. در عوض، فقط ستون هایی را انتخاب کنید که استفاده می شوند.

در مثال زیر، 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)
        // ...
    }
}

جاوا

// This is not the most efficient way of doing this.
// See the following example for a better approach.

try (Cursor cursor = db.rawQuery("""
    SELECT id, name, phone
    FROM customers;
    """, null)) {
  while (cursor.moveToNext()) {
    String name = cursor.getString(1);
    ...
  }
}

با این حال، شما فقط به ستون name نیاز دارید:

کاتلین

db.rawQuery("""
    SELECT name
    FROM Customers;
    """.trimIndent(),
    null
).use { cursor ->
    while (cursor.moveToNext()) {
        val name = cursor.getString(0)
        ...
    }
}

جاوا

try (Cursor cursor = db.rawQuery("""
    SELECT name
    FROM Customers;
    """, null)) {
  while (cursor.moveToNext()) {
    String name = cursor.getString(0);
    ...
  }
}

پرس و جوها را با کارت های SQL پارامتر کنید، نه با الحاق رشته ها

رشته کوئری شما ممکن است شامل پارامتری باشد که فقط در زمان اجرا شناخته شده است، مانند موارد زیر:

کاتلین

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

جاوا

@Nullable
public String getNameById(long id) {
  try (Cursor cursor = db.rawQuery(
      "SELECT name FROM customers WHERE id=" + id, null)) {
    if (cursor.moveToFirst()) {
      return cursor.getString(0);
    } else {
      return 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
        }
    }
}

جاوا

@Nullable
public String getNameById(long id) {
  try (Cursor cursor = db.rawQuery("""
          SELECT name
          FROM customers
          WHERE id=?
      """, new String[] {String.valueOf(id)})) {
    if (cursor.moveToFirst()) {
      return cursor.getString(0);
    } else {
      return null;
    }
  }
}

اکنون می توان پرس و جو را یک بار کامپایل و کش کرد. کوئری کامپایل شده مجدداً بین فراخوانی های مختلف getNameById(long) استفاده می شود.

تکرار در SQL، نه در کد

به جای تکرار یک حلقه برنامه‌ریزی بر روی کوئری‌های SQL برای برگرداندن نتایج منفرد، از یک پرس‌وجو استفاده کنید که همه نتایج هدف را برمی‌گرداند. حلقه برنامه نویسی حدود 1000 برابر کندتر از یک پرس و جوی 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
        ...
    }
}

جاوا

try (Cursor cursor = db.rawQuery("""
    SELECT DISTINCT name
    FROM Customers;
    """, null)) {
  while (cursor.moveToNext()) {
    // Only iterate over distinct names in Java
    ...
  }
}

تا حد امکان از توابع جمع استفاده کنید

از توابع انبوه برای نتایج انبوه بدون داده ردیف استفاده کنید. به عنوان مثال، کد زیر بررسی می کند که آیا حداقل یک ردیف منطبق وجود دارد یا خیر:

کاتلین

// 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
        ...
    } else {
        // No customers from Paris
        ...
}

جاوا

// This is not the most efficient way of doing this.
// See the following example for a better approach.

try (Cursor cursor = db.rawQuery("""
    SELECT id, name
    FROM Customers
    WHERE city = 'Paris';
    """, null)) {
  if (cursor.moveToFirst()) {
    // At least one customer from Paris
    ...
  } else {
    // No customers from Paris
    ...
  }
}

فقط برای واکشی ردیف اول، می توانید از 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
        ...
    } else {
        // No customers from Paris
        ...
    }
}

جاوا

try (Cursor cursor = db.rawQuery("""
    SELECT EXISTS (
      SELECT null
      FROM Customers
      WHERE city = 'Paris'
    );
    """, null)) {
  if (cursor.moveToFirst() && cursor.getInt(0) == 1) {
    // At least one customer from Paris
    ...
  } else {
    // No customers from Paris
    ...
  }
}

از توابع جمع آوری SQLite در کد برنامه خود استفاده کنید:

  • COUNT : تعداد سطرها در یک ستون را شمارش می کند.
  • SUM : تمام مقادیر عددی را در یک ستون اضافه می کند.
  • MIN یا MAX : کمترین یا بالاترین مقدار را تعیین می کند. برای ستون های عددی، انواع DATE و انواع متن کار می کند.
  • AVG : مقدار عددی متوسط ​​را پیدا می کند.
  • GROUP_CONCAT : رشته ها را با جداکننده اختیاری به هم متصل می کند.

استفاده از COUNT() به جای Cursor.getCount()

در مثال زیر، تابع 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()
}

جاوا

// This is not the most efficient way of doing this.
// See the following example for a better approach.

try (Cursor cursor = db.rawQuery("""
    SELECT id
    FROM Customers;
    """, null)) {
  int count = cursor.getCount();
  ...
}

با این حال، با استفاده از COUNT() ، پایگاه داده فقط تعداد را برمی گرداند:

کاتلین

db.rawQuery("""
    SELECT COUNT(*)
    FROM Customers;
    """.trimIndent(),
    null
).use { cursor ->
    cursor.moveToFirst()
    val count = cursor.getInt(0)
}

جاوا

try (Cursor cursor = db.rawQuery("""
    SELECT COUNT(*)
    FROM Customers;
    """, null)) {
  cursor.moveToFirst();
  int count = cursor.getInt(0);
  ...
}

جستجوهای Nest به جای کد

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()) {
                ...
            }
        }
    }
}

جاوا

// This is not the most efficient way of doing this.
// See the following example for a better approach.

try (Cursor cursor = db.rawQuery("""
    SELECT city
    FROM Customers
    GROUP BY city
    ORDER BY COUNT(*) DESC
    LIMIT 1;
    """, null)) {
  if (cursor.moveToFirst()) {
    String topCity = cursor.getString(0);
    try (Cursor innerCursor = db.rawQuery("""
        SELECT name, city
        FROM Customers
        WHERE city = ?;
        """, new String[] {topCity})) {
        while (innerCursor.moveToNext()) {
          ...
        }
    }
  }
}

برای به دست آوردن نتیجه در نیمی از زمان مثال قبلی، از یک پرس و جو 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()) {
        ...
    }
}

جاوا

try (Cursor cursor = db.rawQuery("""
    SELECT name, city
    FROM Customers
    WHERE city IN (
      SELECT city
      FROM Customers
      GROUP BY city
      ORDER BY COUNT(*) DESC
      LIMIT 1
    );
    """, null)) {
  while(cursor.moveToNext()) {
    ...
  }
}

منحصر به فرد بودن در 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
    )
)

جاوا

// This is not the most efficient way of doing this.
// See the following example for a better approach.

try (Cursor cursor = db.rawQuery("""
    SELECT EXISTS (
      SELECT null
      FROM customers
      WHERE username = ?
    );
    """, new String[] { customer.username })) {
  if (cursor.moveToFirst() && cursor.getInt(0) == 1) {
    throw new AddCustomerException(customer);
  }
}
db.execSQL(
    "INSERT INTO customers VALUES (?, ?, ?)",
    new String[] {
      String.valueOf(customer.id),
      customer.name,
      customer.username,
    });

به جای بررسی محدودیت منحصر به فرد در Kotlin یا Java، می توانید آن را در 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)
}

جاوا

try {
  db.execSQL(
      "INSERT INTO Customers VALUES (?, ?, ?)",
      new String[] {
        String.valueOf(customer.id),
        customer.name,
        customer.username,
      });
} catch (SQLiteConstraintException e) {
  throw new AddCustomerException(customer, e);
}

SQLite از شاخص های منحصر به فرد با چندین ستون پشتیبانی می کند:

CREATE TABLE table(...);
CREATE UNIQUE INDEX unique_table ON table(column1, column2, ...);

SQLite محدودیت ها را سریعتر و با سربار کمتری نسبت به کد کاتلین یا جاوا تأیید می کند. بهترین روش استفاده از SQLite به جای کد برنامه است.

درج های چندگانه در یک تراکنش واحد

یک تراکنش چندین عملیات را انجام می دهد که نه تنها کارایی بلکه صحت را نیز بهبود می بخشد. برای بهبود ثبات داده ها و تسریع عملکرد، می توانید درج های دسته ای:

کاتلین

db.beginTransaction()
try {
    customers.forEach { customer ->
        db.execSql(
            "INSERT INTO Customers VALUES (?, ?, ...)",
            arrayOf(customer.id.toString(), customer.name, ...)
        )
    }
} finally {
    db.endTransaction()
}

جاوا

db.beginTransaction();
try {
  for (customer : Customers) {
    db.execSQL(
        "INSERT INTO Customers VALUES (?, ?, ...)",
        new String[] {
          String.valueOf(customer.id),
          customer.name,
          ...
        });
  }
} finally {
  db.endTransaction()
}

از ابزارهای عیب یابی استفاده کنید

SQLite ابزارهای عیب یابی زیر را برای کمک به اندازه گیری عملکرد ارائه می دهد.

از اعلان تعاملی SQLite استفاده کنید

برای اجرای پرس و جوها و یادگیری، SQLite را روی دستگاه خود اجرا کنید. نسخه های مختلف پلتفرم اندروید از نسخه های مختلف SQLite استفاده می کنند. برای استفاده از موتور مشابهی که بر روی یک دستگاه مجهز به اندروید است، از 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

SQLite رابط خط فرمان sqlite3_analyzer (CLI) را برای جمع آوری اطلاعات اضافی که می تواند برای عیب یابی عملکرد استفاده شود، ارائه می دهد. برای نصب، به صفحه دانلود SQLite مراجعه کنید.

می توانید از adb pull برای دانلود یک فایل پایگاه داده از دستگاه مورد نظر به ایستگاه کاری خود برای تجزیه و تحلیل استفاده کنید:

adb pull /data/data/<app_package_name>/databases/<db_name>.db

مرورگر SQLite

همچنین می‌توانید ابزار GUI SQLite Browser را در صفحه دانلودهای SQLite نصب کنید.

لاگ اندروید

Android times SQLite پرس و جو می کند و آنها را برای شما ثبت می کند:

# Enable query time logging
$ adb shell setprop log.tag.SQLiteTime VERBOSE
# Disable query time logging
$ adb shell setprop log.tag.SQLiteTime ERROR
```### Perfetto tracing

### Perfetto tracing {:#perfetto-tracing}

When [configuring Perfetto](https://perfetto.dev/docs/concepts/config), you may
add the following to include tracks for individual queries:

```protobuf
data_sources {
  config {
    name: "linux.ftrace"
    ftrace_config {
      atrace_categories: "database"
    }
  }
}
{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}