SQLite performansı için en iyi uygulamalar

Android, verimli bir SQL veritabanı olan SQLite için yerleşik destek sunar. Uygulamanızın performansını optimize etmek için aşağıdaki en iyi uygulamaları izleyin. Böylece, verileriniz büyüdükçe uygulamanızın hızlı ve öngörülebilir şekilde hızlı kalmasını sağlayabilirsiniz. Bu en iyi uygulamaları kullanarak, yeniden üretilmesi ve sorun giderilmesi zor olan performans sorunlarıyla karşılaşma olasılığını da azaltırsınız.

Daha hızlı performans elde etmek için aşağıdaki performans ilkelerini uygulayın:

  • Daha az satır ve sütun okuma: Sorgularınızı yalnızca gerekli verileri alacak şekilde optimize edin. Aşırı veri alımı performansı etkileyebileceğinden, veritabanından okunan veri miktarını en aza indirin.

  • İşi SQLite motoruna aktarma: SQL sorgularında hesaplama, filtreleme ve sıralama işlemleri gerçekleştirin. SQLite'ın sorgu motorunu kullanmak performansı önemli ölçüde artırabilir.

  • Veritabanı şemasını değiştirme: SQLite'ın etkili sorgu planları ve veri temsilleri oluşturmasına yardımcı olacak şekilde veritabanı şemanızı tasarlayın. Tabloları düzgün şekilde dizine ekleyin ve performansı artırmak için tablo yapılarını optimize edin.

Ayrıca, optimizasyon gerektiren alanları belirlemek için SQLite veritabanınızın performansını ölçmek üzere mevcut sorun giderme araçlarını kullanabilirsiniz.

Jetpack Room kitaplığını kullanmanızı öneririz.

Veritabanını performans için yapılandırma

SQLite'te veritabanınızı optimum performans için yapılandırmak üzere bu bölümdeki adımları uygulayın.

Yazma öncesi günlük kaydını etkinleştirme

SQLite, mutasyonları bir günlük dosyasına ekleyerek uygular. Bu günlük dosyası, zaman zaman veritabanında sıkıştırılır. Buna Yazma Öncesi Günlük Kaydı (WAL) denir.

ATTACH DATABASE kullanmıyorsanız WAL'ı etkinleştirin.

Senkronizasyon modunu rahatlatma

WAL kullanılırken varsayılan olarak her commit, verilerin diske ulaşmasını sağlamak için fsync yayınlar. Bu, verilerin dayanıklılığını artırır ancak işlemelerinizi yavaşlatır.

SQLite'te senkron modunu kontrol etme seçeneği vardır. WAL'ı etkinleştirirseniz senkron modu NORMAL olarak ayarlayın:

Kotlin

db.execSQL("PRAGMA synchronous = NORMAL")

Java

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

Bu ayarda, veriler diske depolanmadan önce bir commit döndürülebilir. Güç kaybı veya çekirdek panik gibi bir cihaz kapatma işlemi gerçekleşirse kaydedilen veriler kaybolabilir. Ancak günlük kaydı nedeniyle veritabanınız bozulmaz.

Yalnızca uygulamanız kilitlenirse verileriniz diske ulaşmaya devam eder. Çoğu uygulama için bu ayar, önemli bir maliyet olmadan performans iyileştirmeleri sağlar.

Verimli tablo şemaları tanımlama

Performansı optimize etmek ve veri tüketimini en aza indirmek için verimli bir tablo şeması tanımlayın. SQLite, verimli sorgu planları ve veriler oluşturarak daha hızlı veri alımına olanak tanır. Bu bölümde, tablo şemaları oluşturmayla ilgili en iyi uygulamalar yer almaktadır.

INTEGER PRIMARY KEY kategorisini düşünün

Bu örnek için bir tabloyu aşağıdaki gibi tanımlayın ve doldurun:

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');

Tablo çıktısı aşağıdaki gibidir:

rowid id ad şehir
1 456 John Lennon Liverpool, İngiltere
2 123 Michael Jackson Gary, Indiana
3 789 Dolly Parton Sevier County, TN

rowid sütunu, ekleme sırasını koruyan bir dizindir. rowid ile filtreleyen sorgular hızlı bir B-ağacı araması olarak uygulanır ancak id ile filtreleyen sorgular yavaş bir tablo taramasıdır.

id ile arama yapmayı planlıyorsanız depolama alanında daha az veri ve genel olarak daha hızlı bir veritabanı için rowid sütununu depolamaktan kaçınabilirsiniz:

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

Tablonuz artık aşağıdaki gibi görünüyor:

id ad şehir
123 Michael Jackson Gary, Indiana
456 John Lennon Liverpool, İngiltere
789 Dolly Parton Sevier County, TN

rowid sütununu saklamanız gerekmediğinden id sorguları hızlıdır. Tablonun artık kampanya siparişine göre değil, id'ya göre sıralandığını unutmayın.

Dizinlerle sorguları hızlandırma

SQLite, sorguları hızlandırmak için dizinleri kullanır. Bir sütun filtrelenirken (WHERE), sıralanırken (ORDER BY) veya toplanırken (GROUP BY) tabloda sütun için bir dizin varsa sorgu hızlandırılır.

Önceki örnekte, city ile filtreleme yapmak için tablonun tamamının taranması gerekir:

SELECT id, name
WHERE city = 'London, England';

Çok sayıda şehir sorgusu olan bir uygulamada, bu sorguları bir dizinle hızlandırabilirsiniz:

CREATE INDEX city_index ON Customers(city);

Dizin, dizin sütununa göre sıralanmış ve rowid ile eşlenmiş ek bir tablo olarak uygulanır:

şehir rowid
Gary, Indiana 2
Liverpool, İngiltere 1
Sevier County, TN 3

city sütununun depolama maliyetinin artık iki katı olduğunu unutmayın. Bunun nedeni, sütunun hem orijinal tabloda hem de dizinde bulunmasıdır. Dizini kullandığınız için eklenen depolama alanının maliyeti, daha hızlı sorguların avantajına değer. Ancak sorgu performansı artışı sağlamadan depolama maliyeti ödememek için kullanmadığınız bir dizini tutmayın.

Çok sütunlu dizinler oluşturma

Sorgularınız birden fazla sütunu birleştiriyorsa sorguyu tam olarak hızlandırmak için çok sütunlu dizinler oluşturabilirsiniz. Ayrıca, dış sütunda bir dizin kullanabilir ve iç aramanın doğrusal tarama olarak yapılmasını sağlayabilirsiniz.

Örneğin, şu sorgu verildiğinde:

SELECT id, name
WHERE city = 'London, England'
ORDER BY city, name

Sorguda belirtilen sırayla aynı olan çok sütunlu bir dizinle sorguyu hızlandırabilirsiniz:

CREATE INDEX city_name_index ON Customers(city, name);

Ancak yalnızca city üzerinde bir dizininiz varsa dış sıralama yine hızlandırılırken iç sıralama için doğrusal tarama gerekir.

Bu özellik, önek sorgularıyla da çalışır. Örneğin, bir dizin ON Customers (city, name), çok sütunlu bir dizinin dizin tablosu belirli bir sırayla belirli dizinlere göre sıralandığından city ile filtreleme, sıralama ve gruplandırma işlemlerini de hızlandırır.

WITHOUT ROWID kategorisini düşünün

SQLite, varsayılan olarak tablonuz için rowid sütunu oluşturur. Burada rowid, örtülü bir INTEGER PRIMARY KEY AUTOINCREMENT'dir. Zaten INTEGER PRIMARY KEY olan bir sütununuz varsa bu sütun, rowid'nin diğer adı olur.

INTEGER dışında bir birincil anahtarı olan veya sütunların bir bileşimi olan tablolar için WITHOUT ROWID'ı kullanabilirsiniz.

Küçük verileri BLOB, büyük verileri ise dosya olarak depolama

Bir satırla büyük verileri (ör. resim küçük resmi veya kişi fotoğrafı) ilişkilendirmek istiyorsanız verileri BLOB sütununda veya bir dosyada depolayabilir, ardından dosya yolunu sütunda saklayabilirsiniz.

Dosyalar genellikle 4 KB'lık artışlarla yuvarlanır. Yuvarlama hatasının önemli olduğu çok küçük dosyaları veritabanında BLOB olarak depolamak daha verimlidir. SQLite, dosya sistemi çağrılarını en aza indirir ve bazı durumlarda temel dosya sisteminden daha hızlıdır.

Sorgu performansını artırma

Yanıt sürelerini en aza indirerek ve işlem verimliliğini en üst düzeye çıkararak SQLite'te sorgu performansını artırmak için aşağıdaki en iyi uygulamalardan yararlanın.

Yalnızca ihtiyacınız olan satırları okuma

Filtreler, tarih aralığı, konum veya ad gibi belirli ölçütleri belirterek sonuçlarınızı daraltmanıza olanak tanır. Sınırlar, gördüğünüz sonuçların sayısını kontrol etmenize olanak tanır:

Kotlin

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

Java

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

Yalnızca ihtiyacınız olan sütunları okuma

Gereksiz sütunları seçmekten kaçının. Bu sütunlar sorgularınızı yavaşlatabilir ve kaynaklarınızı boşa harcayabilir. Bunun yerine, yalnızca kullanılan sütunları seçin.

Aşağıdaki örnekte id, name ve phone seçilir:

Kotlin

// 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)
        // ...
    }
}

Java

// 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);
    ...
  }
}

Ancak yalnızca name sütununa ihtiyacınız vardır:

Kotlin

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

Java

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

Sorguları parametrelendirme

Sorgu dizeniz, yalnızca çalışma zamanında bilinen bir parametre içerebilir. Örneğin:

Kotlin

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

Java

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

Önceki kodda her sorgu farklı bir dize oluşturduğundan ifade önbelleğinden yararlanmaz. Her çağrının yürütülebilmesi için önce SQLite tarafından derlenmesi gerekir. Bunun yerine, id bağımsız değişkenini bir parametreyle değiştirebilir ve değeri selectionArgs ile bağlayabilirsiniz:

Kotlin

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

Java

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

Artık sorgu bir kez derlenip önbelleğe alınabilir. Derlenen sorgu, getNameById(long) öğesinin farklı çağrıları arasında yeniden kullanılır.

Kodda değil SQL'de yineleme yapma

Tek tek sonuç döndürmek için SQL sorgularında yineleme yapan programatik bir döngü yerine, hedeflenen tüm sonuçları döndüren tek bir sorgu kullanın. Programatik döngü, tek bir SQL sorgusundan yaklaşık 1.000 kat daha yavaştır.

Benzersiz değerler için DISTINCT kullanın

DISTINCT anahtar kelimesini kullanmak, işlenmesi gereken veri miktarını azaltarak sorgularınızın performansını artırabilir. Örneğin, bir sütundan yalnızca benzersiz değerleri döndürmek istiyorsanız DISTINCT işlevini kullanın:

Kotlin

db.rawQuery("""
    SELECT DISTINCT name
    FROM Customers;
    """.trimIndent(),
    null
).use { cursor ->
    while (cursor.moveToNext()) {
        // Only iterate over distinct names in Kotlin
        ...
    }
}

Java

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

Mümkün olduğunda toplama işlevlerini kullanın

Satır verileri olmadan toplu sonuçlar için toplama işlevlerini kullanın. Örneğin, aşağıdaki kod en az bir eşleşen satır olup olmadığını kontrol eder:

Kotlin

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

Java

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

Yalnızca ilk satırı getirmek için eşleşen bir satır yoksa EXISTS() işlevini kullanarak 0, bir veya daha fazla satır eşleşiyorsa 1 değerini döndürebilirsiniz:

Kotlin

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

Java

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

Uygulama kodunuzda SQLite toplama işlevlerini kullanın:

  • COUNT: Bir sütunda kaç satır olduğunu sayar.
  • SUM: Bir sütundaki tüm sayısal değerleri toplar.
  • MIN veya MAX: En düşük ya da en yüksek değeri belirler. Sayısal sütunlar, DATE türleri ve metin türleri için çalışır.
  • AVG: Sayısal değerin ortalamasını bulur.
  • GROUP_CONCAT: Dizeleri isteğe bağlı bir ayırıcıyla birleştirir.

Cursor.getCount() yerine COUNT() kullanın

Aşağıdaki örnekte, Cursor.getCount() işlevi veritabanındaki tüm satırları okur ve tüm satır değerlerini döndürür:

Kotlin

// 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()
}

Java

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

Ancak COUNT() kullanıldığında veritabanı yalnızca sayıyı döndürür:

Kotlin

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

Java

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

Kod yerine iç içe yerleştirilmiş sorgular

SQL, birleştirilebilir ve alt sorguları, birleştirmeleri ve yabancı anahtar kısıtlamalarını destekler. Uygulama kodunu incelemeden bir sorgunun sonucunu başka bir sorguda kullanabilirsiniz. Bu, SQLite'ten veri kopyalama ihtiyacını azaltır ve veritabanı motorunun sorgunuzu optimize etmesine olanak tanır.

Aşağıdaki örnekte, en çok müşteriye sahip şehri bulmak için bir sorgu çalıştırabilir, ardından bu şehirden tüm müşterileri bulmak için sonucu başka bir sorguda kullanabilirsiniz:

Kotlin

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

Java

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

Önceki örneğin yarısı kadar sürede sonucu almak için iç içe yerleştirilmiş ifadeler içeren tek bir SQL sorgusu kullanın:

Kotlin

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

Java

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'de benzersizliği kontrol etme

Bir satırın, belirli bir sütun değeri tabloda benzersiz olmadığı sürece eklenmemesi gerekiyorsa bu benzersizliği sütun kısıtlaması olarak zorunlu kılmak daha verimli olabilir.

Aşağıdaki örnekte, eklenecek satırı doğrulamak için bir sorgu, satırı eklemek içinse başka bir sorgu çalıştırılır:

Kotlin

// 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
    )
)

Java

// 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 veya Java'da benzersiz kısıtlamayı kontrol etmek yerine tabloyu tanımlarken SQL'de kontrol edebilirsiniz:

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

SQLite, aşağıdakilerle aynı işlemi yapar:

CREATE TABLE Customers(...);
CREATE UNIQUE INDEX CustomersUsername ON Customers(username);

Artık bir satır ekleyebilir ve kısıtlamayı SQLite'ın kontrol etmesini sağlayabilirsiniz:

Kotlin

try {
    db.execSql(
        "INSERT INTO Customers VALUES (?, ?, ?)",
        arrayOf(customer.id.toString(), customer.name, customer.username)
    )
} catch(e: SQLiteConstraintException) {
    throw AddCustomerException(customer, e)
}

Java

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, birden çok sütun içeren benzersiz dizinleri destekler:

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

SQLite, kısıtlamaları Kotlin veya Java koduna göre daha hızlı ve daha az ek yükle doğrular. Uygulama kodu yerine SQLite kullanmak en iyi uygulamadır.

Tek bir işlemde birden fazla ekleme işlemini toplu olarak gerçekleştirme

Bir işlem, birden fazla işlemi yürütür. Bu da yalnızca verimliliği değil, doğruluğu da artırır. Veri tutarlılığını artırmak ve performansı hızlandırmak için eklemeleri toplu olarak yapabilirsiniz:

Kotlin

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

Java

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

Sorun giderme araçlarını kullanma

SQLite, performansı ölçmeye yardımcı olmak için aşağıdaki sorun giderme araçlarını sağlar.

SQLite'ın etkileşimli istemini kullanma

Sorgu çalıştırmak ve bilgi edinmek için makinenizde SQLite'ı çalıştırın. Farklı Android platform sürümleri, SQLite'ın farklı düzeltmelerini kullanır. Android destekli bir cihazdaki motoru kullanmak için adb shell kullanın ve hedef cihazınızda sqlite3 komutunu çalıştırın.

SQLite'tan sorguları zamanlamasını isteyebilirsiniz:

sqlite> .timer on
sqlite> SELECT ...
Run Time: real ... user ... sys ...

EXPLAIN QUERY PLAN

SQLite'tan EXPLAIN QUERY PLAN kullanarak bir sorguyu nasıl yanıtlayacağını açıklamasını isteyebilirsiniz:

sqlite> EXPLAIN QUERY PLAN
SELECT id, name
FROM Customers
WHERE city = 'Paris';
QUERY PLAN
`--SCAN Customers

Önceki örnekte, Paris'teki tüm müşterileri bulmak için dizin olmadan tam tablo taraması yapılması gerekir. Buna doğrusal karmaşıklık denir. SQLite'ın tüm satırları okuması ve yalnızca Paris'teki müşterilerle eşleşen satırları tutması gerekir. Bu sorunu düzeltmek için dizin ekleyebilirsiniz:

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=?

Etkileşimli kabuğu kullanıyorsanız SQLite'tan sorgu planlarını her zaman açıklamasını isteyebilirsiniz:

sqlite> .eqp on

Daha fazla bilgi için Sorgu Planlama bölümüne bakın.

SQLite Analyzer

SQLite, performansla ilgili sorunları gidermek için kullanılabilecek ek bilgileri boşaltmak üzere sqlite3_analyzer komut satırı arayüzü (KSA) sunar. Yüklemek için SQLite İndirme Sayfası'nı ziyaret edin.

adb pull simgesini kullanarak hedef cihazdaki bir veritabanı dosyasını analiz için iş istasyonunuza indirebilirsiniz:

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

SQLite Tarayıcısı

Ayrıca, SQLite İndirmeler sayfasında SQLite Browser adlı GUI aracını da yükleyebilirsiniz.

Android günlük kaydı

Android, SQLite sorgularının süresini ölçer ve bunları sizin için günlüğe kaydeder:

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

Perfetto izleme

Perfetto'yu yapılandırırken, tek tek sorgular için izlemeleri dahil etmek üzere aşağıdakileri ekleyebilirsiniz:

data_sources {
  config {
    name: "linux.ftrace"
    ftrace_config {
      atrace_categories: "database"
    }
  }
}

dumpsys meminfo

adb shell dumpsys meminfo <package-name>, SQLite belleğiyle ilgili bazı ayrıntılar da dahil olmak üzere uygulamanın bellek kullanımıyla ilgili istatistikleri yazdırır. Örneğin, bu, bir geliştiricinin cihazında adb shell dumpsys meminfo com.google.android.gms.persistent komutunun çıkışından alınmıştır:

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

DATABASES bölümünde şunları bulabilirsiniz:

  • pgsz: Bir veritabanı sayfasının boyutu (KB cinsinden).
  • dbsz: Veritabanının tamamının boyutu (sayfa olarak). Boyutu KB cinsinden almak için pgsz ile dbsz değerini çarpın.
  • Lookaside(b): Bağlantı başına SQLite lookaside arabelleğine ayrılan bellek (bayt cinsinden). Bunlar genellikle çok küçüktür.
  • cache hits: SQLite, veritabanı sayfalarının önbelleğini tutar. Bu, sayfa önbelleği isabetlerinin sayısıdır (sayım).
  • cache misses: Sayfa önbelleği isabet etmeme sayısı (sayım).
  • cache size: Önbellekteki sayfa sayısı (adet). Boyutu KB cinsinden almak için bu sayıyı pgsz ile çarpın.
  • Dbname: DB dosyasının yolu. Örneğimizde, aynı temel veritabanına birden fazla bağlantı olduğunu belirtmek için bazı veritabanlarının adlarına (1) veya başka bir sayı eklenmiştir. İstatistikler bağlantı başına izlenir.

POOL STATS bölümünde şunları bulabilirsiniz:

  • cache hits: SQLite, hazırlanan ifadeleri önbelleğe alır ve SQL ifadelerini derlerken biraz çaba ve bellek tasarrufu sağlamak için sorguları çalıştırırken bunları yeniden kullanmaya çalışır. Bu, ifade önbelleği isabetlerinin sayısıdır.
  • cache misses: İfade önbelleği kaçırma sayısı (sayım).
  • cache size: Android 17'den itibaren bu, önbellekteki toplam hazırlanmış ifade sayısını listeler. Önceki sürümlerde bu değer, diğer iki sütunda listelenen isabet ve ıskaların toplamına eşittir ve önbellek boyutunu temsil etmez.