הגדרת SQLite: מיגרציות, אינדקסים, WAL לאפליקציות מובייל

ביצועי מסד נתונים איטיים ושגיאות migration מעכבים את פיתוח אפליקציות המובייל ופוגעים בחוויית המשתמש. אנו מגדירים SQLite במתכונת turnkey: מתכננים את ה-schema, מיישמים migrations, מייעלים indexes ומפעילים מצב WAL לפעילות יציבה. הצוות שלנו מטפל בכל המחזור, מבדיקת נאותות ועד תמיכה, כך שתקבלו פתרון אמין שגדל עם העסק שלכם.

פיתוח ותמיכה בכל סוגי היישומים הניידים:

יישומים ניידים למידע ובידור
אפליקציות חדשות, משחקים, מדריכים, קטלוגים מקוונים, אפליקציות מזג אוויר, אפליקציות כושר ובריאות, אפליקציות נסיעות, אפליקציות חינוכיות, רשתות חברתיות ומסנג'רים, חידונים, בלוגים ופודקאסטים, פורומים, אגרגטורים
יישומים ניידים למסחר אלקטרוני
חנויות מקוונות, אפליקציות B2B, מרקטפלייסים, בורסות מקוונות, שירותי קאשבק, בורסות, פלטפורמות דרופשיפינג, תוכניות נאמנות, משלוחי מזון ומוצרים, מערכות תשלום.
יישומים ניידים לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, ניהול פרויקטים, כלים לצוותי מכירות, ניהול פיננסי, ניהול ייצור, ניהול לוגיסטיקה ומשלוחים, ניהול משאבי אנוש, מערכות ניטור נתונים
יישומים ניידים לשירותים אלקטרוניים
פלטפורמות מודעות מסווגות, בתי ספר מקוונים, בתי קולנוע מקוונים, פלטפורמות שירות אלקטרוניות, פלטפורמות קאשבק, אירוח וידאו, פורטלים נושאיים, פלטפורמות הזמנות ותזמון מקוונות, פלטפורמות מסחר מקוונות

אלה הם רק חלק מסוגי היישומים הניידים שאנו עובדים איתם, ולכל אחד מהם עשויים להיות מאפיינים ופונקציונליות ספציפיים משלו, המותאמים לצרכים ולמטרות הספציפיות של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 1734 השירותים
הגדרת SQLite: מיגרציות, אינדקסים, WAL לאפליקציות מובייל
בינוני
מ- 1 יום עד 3 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית מובייל עבור FEEDME
    פיתוח אפליקציית מובייל עבור FEEDME
    971
  • פיתוח אפליקציית מובייל עבור XOOMER
    פיתוח אפליקציית מובייל עבור XOOMER
    869
  • פיתוח אפליקציית מובייל עבור RHL
    פיתוח אפליקציית מובייל עבור RHL
    1289
  • פיתוח אפליקציית מובייל עבור ZIPPY
    פיתוח אפליקציית מובייל עבור ZIPPY
    1143
  • פיתוח אפליקציית מובייל עבור Affhome
    פיתוח אפליקציית מובייל עבור Affhome
    1076
  • פיתוח אפליקציית מובייל עבור חברת FLAVORS
    פיתוח אפליקציית מובייל עבור חברת FLAVORS
    677

לעתים קרובות אנו נתקלים בפרויקטים שבהם SQLite משמש כערימת מפתח-ערך פשוטה. לאחר שישה חודשים, קוד כזה הופך לסיוט של שאילתות גולמיות, SQLiteDatabaseLockedException באנדרואיד, וקריסות במהלך מיגרציות. ללא הגדרה נכונה, מסד הנתונים הופך לצוואר בקבוק: 9 מתוך 10 ביקורות על ביצועים איטיים של אפליקציה מקושרות לשאילתות לא מותאמות. בפרויקט אחד, צמצמנו את זמן טעינת הרשימה מ-8 שניות ל-0.3 שניות — פשוט על ידי הוספת אינדקס והפעלת WAL. הצוות שלנו עם ניסיון של 10+ שנים בפיתוח מובייל מגדיר את SQLite כך שמסד הנתונים יפעל ביציבות ויתקנה ללא כאבים.

SQLite מוטמע ב-iOS וב-Android ברמת מערכת ההפעלה. השאלה היא לא האם לשלב אותו — הוא כבר שם. השאלה היא איך לעבוד איתו כך ששישה חודשים לאחר מכן לא תצטרכו לשכתב הכל מאפס בגלל שאילתות גולמיות סבוכות או קריסות עם SQLiteDatabaseLockedException. ארכיטקטורה נכונה עם ORM ומצב WAL מבטלת 80% מהבעיות הטיפוסיות.

איך לבחור ORM לפרויקט שלך?

עבודה ישירה עם SQLite דרך android.database.sqlite.SQLiteDatabase או sqlite3 ב-iOS היא אפשרות לתרחישים מינימליים. בפרויקטים אמיתיים, משתמשים ב-ORM:

פלטפורמה ספרייה גישה ביצועים
Android Room (Jetpack) annotations + DAO גבוהים, עם WAL עד 5000 כתיבות/שנייה
iOS GRDB.swift שאילתות Swift בטוחות טיפוסית דומים ל-SQL גולמי
Flutter sqflite + drift codegen + reactive בינוניים, אבל נוחים
React Native react-native-sqlite-storage / op-sqlite SQL גולמי או TypeORM תלוי ב-wrapper
Multiplatform SQLDelight סכמת SQL משותפת גבוהים, מייצר קוד native

Room הוא הסטנדרט לאנדרואיד, מגובה על ידי Google. GRDB.swift ב-iOS מספק שאילתות בטוחות טיפוסית ללא קסם מיותר. SQLDelight מעניין לפרויקטים של KMM: קובץ .sq אחד עם SQL מייצר קוד Kotlin ו-Swift.

Room באנדרואיד: ארכיטקטורה נכונה

@Entity(tableName = "products", indices = [Index(value = ["category_id"]), Index(value = ["sku"], unique = true)] )
data class ProductEntity(
    @PrimaryKey val id: String,
    @ColumnInfo(name = "category_id") val categoryId: String,
    val sku: String,
    val title: String,
    @ColumnInfo(name = "price_cents") val priceCents: Int,
    @ColumnInfo(name = "updated_at") val updatedAt: Long,
    @ColumnInfo(name = "is_deleted") val isDeleted: Boolean = false
)

@Dao
interface ProductDao {
    @Query("SELECT * FROM products WHERE category_id = :categoryId AND is_deleted = 0 ORDER BY title ASC")
    fun observeByCategory(categoryId: String): Flow<List<ProductEntity>>

    @Upsert
    suspend fun upsert(products: List<ProductEntity>)

    @Query("UPDATE products SET is_deleted = 1, updated_at = :timestamp WHERE id = :id")
    suspend fun softDelete(id: String, timestamp: Long)
}

@Entity(tableName = "products", indices = [Index(value = ["category_id"]), Index(value = ["sku"], unique = true)] ) data class ProductEntity( @PrimaryKey val id: String, @ColumnInfo(name = "category_id") val categoryId: String, val sku: String, val title: String, @ColumnInfo(name = "price_cents") val priceCents: Int, @ColumnInfo(name = "updated_at") val updatedAt: Long, @ColumnInfo(name = "is_deleted") val isDeleted: Boolean = false ) @Dao interface ProductDao { @Query("SELECT * FROM products WHERE category_id = :categoryId AND is_deleted = 0 ORDER BY title ASC") fun observeByCategory(categoryId: String): Flow<List<ProductEntity>> @Upsert suspend fun upsert(products: List<ProductEntity>) @Query("UPDATE products SET is_deleted = 1, updated_at = :timestamp WHERE id = :id") suspend fun softDelete(id: String, timestamp: Long) } הופיע ב-Room 2.5 — לפני כן היה צורך ב-@Upsert. מחיקה רכה באמצעות דגל @Insert(onConflict = OnConflictStrategy.REPLACE) היא נוהג סטנדרטי לסנכרון מסדי נתונים, כך שרשומה לא אובדת עד שאישור המחיקה מתקבל מהשרת.

מיגרציות — הנקודה הכואבת ביותר

Room בודק is_deleted כאשר הסכמה משתנה. אם exportedSchema מוגדר, מסד הנתונים נוצר מחדש בכל שינוי סכמה. זה בסדר לדיבאג אבל לא מקובל לייצור.

val db = Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
    .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
    .build()

val MIGRATION_2_3 = object : Migration(2, 3) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE products ADD COLUMN tags TEXT NOT NULL DEFAULT ''")
        db.execSQL("CREATE INDEX IF NOT EXISTS index_products_updated_at ON products(updated_at)")
    }
}

ייצא את הסכמה ל-JSON (fallbackToDestructiveMigration() ב-val db = Room.databaseBuilder(context, AppDatabase::class.java, "app.db") .addMigrations(MIGRATION_1_2, MIGRATION_2_3) .build() val MIGRATION_2_3 = object : Migration(2, 3) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("ALTER TABLE products ADD COLUMN tags TEXT NOT NULL DEFAULT ''") db.execSQL("CREATE INDEX IF NOT EXISTS index_products_updated_at ON products(updated_at)") } } ) ואחסן אותה ב-git. במהלך בדיקת קוד, שינויי סכמה נראים מיד. Room יכול ליצור מיגרציה אוטומטית דרך room.schemaLocation למקרים פשוטים (הוספת עמודה), אבל שינוי שמות של טבלאות ועמודות דורש annotations מסוג build.gradle/AutoMigration. אסטרטגיית מיגרציה נכונה חוסכת עד 30% מזמן התחזוקה.

GRDB.swift ב-iOS

// Открытие и настройка
let dbQueue = try DatabaseQueue(path: dbPath)
try dbQueue.write { db in
    try db.create(table: "products", ifNotExists: true) { t in
        t.primaryKey("id", .text)
        t.column("category_id", .text).notNull().indexed()
        t.column("sku", .text).unique()
        t.column("title", .text).notNull()
        t.column("price_cents", .integer).notNull()
        t.column("updated_at", .integer).notNull()
    }
}
// Реактивное наблюдение через ValueObservation
let observation = ValueObservation.tracking { db in
    try Product.filter(Column("categoryId") == categoryId).fetchAll(db)
}
let cancellable = observation.start(in: dbQueue, onError: { error in
    print(error)
}, onChange: { products in
    self.updateUI(products)
} )

@RenameTable מקביל ל-Flow של Room: הוא מפעיל מחדש את השאילתה אוטומטית כאשר הטבלאות המושפעות משתנות.

איך מצב WAL משפר ביצועים?

כברירת מחדל, SQLite עובד במצב journal. עבור אפליקציות מובייל, WAL (Write-Ahead Logging) עדיף: קוראים לא חוסמים כותבים. Room מפעיל WAL אוטומטית. ב-GRDB: @RenameColumn. בדיקות מראות ש-WAL מפחית את זמן ההשהיה בכתיבה פי 3 באנדרואיד ופי 5 ב-iOS. זה בולט במיוחד עם הוספות תכופות — למשל, בעת טעינת נתונים לא מקוונים. לפרטים נוספים על WAL, ראו SQLite WAL.

אילו אינדקסים ליצור להאצת שאילתות?

אינדקסים על שדות ב-WHERE ו-ORDER BY מפחיתים את זמן הסריקה פי 10–100. בלעדיהם, SQLite מבצע סריקת טבלה מלאה. עבור טבלת הזמנות עם 100,000 שורות, שאילתה לפי תאריך ללא אינדקס אורכת 2 שניות; עם אינדקס, 20 אלפיות השנייה. צור אינדקסים ייחודיים על SKU, אימייל — הם מבטיחים שלמות ומאיצים חיפושים. איזון אופטימלי: לא יותר מ-5 אינדקסים לטבלה; כל אינדקס מאט כתיבות ב-10-20%.

שגיאות טיפוסיות ואיך להימנע מהן

בעיות SQLite נפוצות באפליקציות מובייל

בעיה טיפוסית היא שאילתות N+1 ב-// Открытие и настройка let dbQueue = try DatabaseQueue(path: dbPath) try dbQueue.write { db in try db.create(table: "products", ifNotExists: true) { t in t.primaryKey("id", .text) t.column("category_id", .text).notNull().indexed() t.column("sku", .text).unique() t.column("title", .text).notNull() t.column("price_cents", .integer).notNull() t.column("updated_at", .integer).notNull() } } // Реактивное наблюдение через ValueObservation let observation = ValueObservation.tracking { db in try Product.filter(Column("categoryId") == categoryId).fetchAll(db) } let cancellable = observation.start(in: dbQueue, onError: { error in print(error) }, onChange: { products in self.updateUI(products) } ) . ValueObservation מחזיר 200 שורות, ואז עבור כל dbQueue.configuration.journalMode = .wal. 200 שאילתות על thread של ממשק המשתמש — RecyclerView תוך 5 שניות על מכשיר אמיתי. פתרון: JOIN או שאילתת אצווה נפרדת SELECT * FROM orders. אנחנו תמיד בודקים מקרים אלה במהלך בדיקת קוד. טעות נפוצה נוספת היא אחסון תמונות ב-BLOB: עדיף לשמור נתיבי קבצים. התעלמות מ-WAL מובילה ל-SELECT * FROM order_items WHERE order_id = ? בגישה מרובת thread.

מה כלול בעבודה

  • עיצוב סכמת מסד הנתונים על בסיס לוגיקה עסקית
  • בחירה והגדרה של ORM (Room, GRDB, SQLDelight) עבור iOS/Android/KMM
  • כתיבת מיגרציות עם אחסון סכמה ב-Git
  • הפעלת מצב WAL ואופטימיזציה של אינדקסים
  • תיעוד על עבודה עם מסד הנתונים והוראות מיגרציה
  • בדיקת קוד ובדיקות על מכשירים אמיתיים

לוחות זמנים ועלות

הגדרת SQLite עם Room או GRDB, אסטרטגיית מיגרציה, אינדקסים: החל משבוע אחד לכל פלטפורמה. העלות מחושבת באופן אישי — אנו מעריכים את הפרויקט ביום אחד. הטמע מסד נתונים מקומי יציב — צור קשר לייעוץ. במשך 10+ שנים השלמנו יותר מ-50 פרויקטים עם אחסון נתונים מקומי — חיסכון ממוצע בתקציב על עיבוד מחדש הוא 30% עם הגדרה ראשונית נכונה. קבל ייעוץ לפרויקט שלך היום.