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 bu en iyi uygulamalardan yararlanarak, verileriniz büyüdükçe uygulamanızın hızlı ve öngörülebilir şekilde hızlı kalmasını sağlayın. Bu en iyi uygulamaları kullanarak, yeniden oluşturması ve gidermesi zor olan performans sorunlarıyla karşılaşma olasılığını da azaltırsınız.

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

  • Daha az satır ve sütun okuyun: 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.

  • İşleri SQLite Engine'e aktarma: SQL sorguları içinde 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ştirin: SQLite'ın verimli sorgu planları ve veri temsilleri oluşturmasına yardımcı olmak için veritabanı şemanızı tasarlayın. Performansı artırmak için tabloları uygun şekilde dizine ekleyin ve tablo yapılarını optimize edin.

Ayrıca, optimizasyon gerektiren alanları belirlemek için SQLite veritabanınızın performansını ölçmek amacıyla mevcut sorun giderme araçlarından yararlanabilirsiniz.

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

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

Veritabanınızı SQLite'ta optimum performans sağlayacak şekilde yapılandırmak için bu bölümdeki adımları uygulayın.

Yazma Öncesi Günlük Kaydını Etkinleştir

SQLite, mutasyonları bir günlüğe ekleyerek uygular ve bunları bazen veritabanına sıkıştırır. Buna Write-Ahead Logging (WAL) (Yazma Önceden Günlük Kaydı) adı verilir.

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

Senkronizasyon modunu gevşetme

WAL kullanırken varsayılan olarak her kaydetme, verilerin diske ulaştığından emin olmak için bir fsync gönderir. Bu, veri dayanıklılığını artırır ancak taahhütlerinizi yavaşlatır.

SQLite'ta eşzamanlı modu kontrol etme seçeneği bulunur. WAL'ı etkinleştirirseniz eşzamanlı modu NORMAL olarak ayarlayın:

Kotlin

db.execSQL("PRAGMA synchronous = NORMAL")

Java

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

Bu ayarda, bir kaydetme, veriler diskte depolanmadan önce döndürülebilir. Güç kaybı veya çekirdek paniği gibi bir cihaz kapanması durumunda, taahhüt edilen veriler kaybolabilir. Ancak günlük kaydı sayesinde veritabanınız bozulmamıştır.

Yalnızca uygulamanız kilitlenirse verileriniz diske ulaşmaya devam eder. Çoğu uygulama için bu ayar, önemli bir maliyet olmaksızın 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 veri alma işlemini hızlandırır. Bu bölümde, tablo şemaları oluşturmayla ilgili en iyi uygulamalar açıklanmaktadır.

INTEGER PRIMARY KEY uygulamasını düşünün

Bu örnekte, bir tabloyu aşağıdaki gibi tanımlayıp 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:

satır kimliği id ad şehir
1 456 daha fazla içerik Liverpool, İngiltere
2 123 Michael Jackson'ın yer aldığı daha fazla içerik Gary, Indiana
3 789 Dolly Parton Sevier İlçesi, New York

rowid sütunu, kampanya siparişini koruyan bir dizindir. rowid ölçütüne göre filtrelenen sorgular hızlı bir B ağacı araması olarak uygulanır ancak id ölçütüne göre filtrelenen sorgular yavaş bir tablo taramasıdır.

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

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

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

id ad şehir
123 Michael Jackson'ın yer aldığı daha fazla içerik Gary, Indiana
456 daha fazla içerik Liverpool, İngiltere
789 Dolly Parton Sevier İlçesi, New York

rowid sütununu depolamanız gerekmediği için id sorguları hızlıdır. Tablonun artık kampanya siparişi yerine id ölçütüne göre sıralandığını unutmayın.

Dizinleri kullanarak sorguları hızlandırın

SQLite, sorguları hızlandırmak için dizinleri kullanır. Bir sütunu filtrelerken (WHERE), sıralarken (ORDER BY) veya toplarken (GROUP BY) tabloda sütun için bir dizin varsa sorgu hızlandırılır.

Önceki örnekte, city ölçütüne göre filtreleme yapmak için tablonun tamamının taranmasını gerektirir.

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

Çok sayıda şehir sorgusu içeren bir uygulamada bu sorguları bir dizinle hızlandırabilirsiniz:

CREATE INDEX city_index ON Customers(city);

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

şehir satır kimliği
Gary, Indiana 2
Liverpool, İngiltere 1
Sevier İlçesi, New York 3

Artık hem orijinal tabloda hem de dizinde mevcut olduğundan city sütununun depolama maliyetinin iki katına çıktığını unutmayın. Dizini kullandığınız için daha hızlı sorguların avantajı, ek depolama alanının maliyetine değecektir. Ancak sorgu performansı kazancı olmadan depolama alanı maliyetini ödemekten kaçınmak için, kullanmadığınız bir dizini tutmayın.

Çok sütunlu dizinler oluşturma

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

Örneğin, aşağıdaki sorguya göre:

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

Sorguyu, sorguda belirtilen sırayla çok sütunlu bir dizinle hızlandırabilirsiniz:

CREATE INDEX city_name_index ON Customers(city, name);

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

Bu yöntem ön ek sorguları için de kullanılabilir. Örneğin, bir dizin ON Customers (city, name) çok sütunlu bir dizine ait dizin tablosu, verilen dizinlere göre belirtilen sırada yer aldığı için city ölçütüne göre filtreleme, sıralama ve gruplandırmayı da hızlandırır.

WITHOUT ROWID uygulamasını düşünün

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

INTEGER dışında bir birincil anahtarı veya sütunların bir bileşimini içeren tablolar için WITHOUT ROWID seçeneğini değerlendirin.

Küçük verileri BLOB, büyük verileri ise dosya olarak depolayın

Büyük verileri bir satırla (ör. bir resmin küçük resmi veya kişi fotoğrafı) ilişkilendirmek isterseniz verileri bir BLOB sütununda veya bir dosyada depolayabilir, ardından dosya yolunu sütunda depolayabilirsiniz.

Dosyalar genellikle 4 KB'lık artışlara yuvarlanır. Yuvarlama hatasının önemli olduğu çok küçük dosyalarda, bunların veritabanında BLOB olarak depolanması 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ı iyileştirme

Yanıt sürelerini en aza indirip işleme verimliliğini en üst düzeye çıkararak SQLite'ta sorgu performansını iyileştirmek için bu en iyi uygulamaları izleyin.

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

Filtreler; tarih aralığı, konum veya ad gibi belirli ölçütleri belirterek sonuçlarınızın kapsamını 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ı okuyun

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

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

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

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, yalnızca bir sütundaki benzersiz değerleri döndürmek istiyorsanız DISTINCT değerini 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, eşleşen en az bir 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 amacıyla, eşleşen satır yoksa 0 değerini döndürmek için EXISTS() ve bir veya daha fazla satır eşleşiyorsa 1 kullanabilirsiniz:

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 veya en yüksek değeri belirler. Sayısal sütunlar, DATE türleri ve metin türlerinde çalışır.
  • AVG: Ortalama sayısal değeri bulur.
  • GROUP_CONCAT: Dizeleri isteğe bağlı bir ayırıcıyla birleştirir.

Cursor.getCount() yerine COUNT() kullan

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 şu 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 Nest sorguları

SQL, oluşturulabilme özelliğine sahiptir ve alt sorguları, birleştirmeleri ve yabancı anahtar kısıtlamalarını destekler. Bir sorgunun sonucunu, uygulama kodunu kullanmadan başka bir sorguda kullanabilirsiniz. Bu, SQLite'tan veri kopyalama ihtiyacını azaltır ve veritabanı motorunun sorgunuzu optimize etmesini sağlar.

Aşağıdaki örnekte, en çok müşterinin hangi şehirde olduğunu bulmak için bir sorgu çalıştırabilir ve ardından sonucu bu şehirdeki tüm müşterileri bulmak için 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()) {
          ...
        }
    }
  }
}

Sonucu bir önceki örneğin yarısında almak için iç içe yerleştirilmiş ifadelere sahip 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

Tabloda belirli bir sütun değeri benzersiz olmadığı sürece bir satırın eklenmemesi gerekiyorsa bu benzersizliğin sütun kısıtlaması olarak uygulanması daha verimli olabilir.

Aşağıdaki örnekte, eklenecek satırı doğrulamak için bir sorgu ve gerçekten eklenecek başka bir sorgu çalıştırılmıştı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'daki 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ğıdaki işlemleri yapar:

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

Artık bir satır ekleyebilir ve SQLite'ın kısıtlamayı 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ütunlu 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ük ile doğrular. Uygulama kodu yerine SQLite kullanmak en iyi uygulamalardan biridir.

Birden fazla eklemeyi tek bir işlemde toplu hale getirme

Bir işlem birden çok işlem gerçekleştirir ve bu yalnızca verimliliği değil, doğruluğu da artırır. Veri tutarlılığını iyileştirmek 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çmenize yardımcı olmak için aşağıdaki sorun giderme araçlarını sağlar.

SQLite'ın etkileşimli istemini kullanın

Sorguları çalıştırmak ve öğrenmek 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 aynı motoru kullanmak için adb shell kullanarak 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ıtlamayı hedeflediğini 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ı 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ı gerekiyor. Bunu düzeltmek için bir 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 kabuk 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 Analiz Aracı

SQLite, performans sorunlarını gidermek için kullanılabilecek ek bilgilerin dökümü için sqlite3_analyzer komut satırı arayüzünü (CLI) sunar. Yüklemek için SQLite İndirme Sayfasını ziyaret edin.

Analiz amacıyla bir hedef cihazdan iş istasyonunuza veritabanı dosyası indirmek için adb pull kullanabilirsiniz:

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

SQLite Tarayıcısı

Ayrıca, SQLite Tarayıcısı GUI aracını SQLite İndirilenler sayfasına yükleyebilirsiniz.

Android günlük kaydı

Android, SQLite sorgularını çarpar ve 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 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"
    }
  }
}