Sqlite के लिए में app डेटाबेस प्रवास के लिए सबसे अच्छा अभ्यास


94

मैं अपने iphone के लिए sqlite का उपयोग कर रहा हूं और मुझे आशा है कि डेटाबेस स्कीमा समय के साथ बदल सकता है। प्रत्येक बार एक सफल प्रवास करने के लिए क्या विचार, नामकरण परंपराएं और चीजें देखने के लिए हैं?

उदाहरण के लिए, मैंने एक संस्करण को डेटाबेस नाम (उदाहरण के लिए Database_v1) से जोड़ने के बारे में सोचा है।

जवाबों:


111

मैं एक एप्लिकेशन को बनाए रखता हूं जिसे समय-समय पर एक साइक्लाइट डेटाबेस को अपडेट करने और पुराने डेटाबेस को नए स्कीमा में स्थानांतरित करने की आवश्यकता होती है और यहां मैं क्या कर रहा हूं:

डेटाबेस संस्करण को ट्रैक करने के लिए, मैं अंतर्निहित उपयोगकर्ता-संस्करण चर का उपयोग करता हूं जो कि sqlite प्रदान करता है (sqlite इस चर के साथ कुछ भी नहीं करता है, आप इसे उपयोग करने के लिए स्वतंत्र हैं हालांकि आप कृपया)। यह 0 से शुरू होता है, और आप निम्न चर बयानों के साथ इस चर को प्राप्त / सेट कर सकते हैं:

> PRAGMA user_version;  
> PRAGMA user_version = 1;

जब एप्लिकेशन शुरू होता है, मैं वर्तमान उपयोगकर्ता-संस्करण की जांच करता हूं, तो स्कीमा को अद्यतित करने के लिए आवश्यक किसी भी परिवर्तन को लागू करें, और फिर उपयोगकर्ता-संस्करण को अपडेट करें। मैं लेन-देन में अद्यतनों को लपेटता हूं ताकि अगर कुछ भी गलत हो जाए, तो परिवर्तन प्रतिबद्ध न हों।

स्कीमा परिवर्तन करने के लिए, कुछ कार्यों के लिए साइक्लाइट "ALTER TABLE" सिंटैक्स का समर्थन करता है (तालिका का नाम बदलकर या स्तंभ जोड़कर)। यह मौजूदा तालिकाओं को इन-प्लेस करने का एक आसान तरीका है। : दस्तावेज़ यहां देखें http://www.sqlite.org/lang_altertable.html । "ALTER TABLE" सिंटैक्स द्वारा समर्थित कॉलम या अन्य परिवर्तनों को हटाने के लिए, मैं एक नई तालिका बनाता हूं, उसमें तारीख को माइग्रेट करता हूं, पुरानी तालिका को छोड़ता हूं, और मूल नाम पर नई तालिका का नाम बदलता हूं।


2
मैं एक ही तर्क देने की कोशिश कर रहा हूं, लेकिन किसी कारण से जब मैं "pragma user_version =?" निष्पादित करता हूं। प्रोग्रामिक रूप से, यह विफल रहता है ... कोई विचार?
यूनिकॉर्न

7
व्यावहारिक सेटिंग्स मापदंडों का समर्थन नहीं करती हैं, आपको वास्तविक मूल्य प्रदान करना होगा: "pragma user_version = 1"।
csgero

2
मेरा एक सवाल है। मान लीजिए कि यदि आप एक प्रारंभिक संस्करण 1. और वर्तमान संस्करण 5 है। संस्करण 2,3,4 में कुछ अपडेट हैं। अंतिम उपयोगकर्ता ने केवल आपका संस्करण 1 डाउनलोड किया, और अब संस्करण 5 में अपग्रेड किया। आपको क्या करना चाहिए?
Bagusflyer

6
डेटाबेस को कई चरणों में अपडेट करें, संस्करण 1 से संस्करण 2 तक जाने के लिए आवश्यक परिवर्तन लागू करें, फिर संस्करण 2 से संस्करण 3 तक, आदि ... जब तक यह अद्यतित नहीं हो जाता। ऐसा करने का एक आसान तरीका स्विच स्टेटमेंट है जहां प्रत्येक "केस" स्टेटमेंट डेटाबेस को एक संस्करण द्वारा अपडेट करता है। आप वर्तमान डेटाबेस संस्करण में "स्विच" करते हैं, और अद्यतन पूरा होने तक केस स्टेटमेंट गिर जाते हैं। जब भी आप डेटाबेस को अपडेट करते हैं, तो बस एक नया केस स्टेटमेंट जोड़ें। इसके विस्तृत उदाहरण के लिए बिली ग्रे द्वारा नीचे दिए गए उत्तर को देखें।
Rngbus

1
@KonstantinTarkus, प्रलेखन के अनुसार डेटाबेस के संस्करणों के लिए नहीं, उदाहरण के लिए उपयोगिता application_idद्वारा फ़ाइल प्रारूप की पहचान करने के लिए एक अतिरिक्त बिट है file
xaizek

30

जस्ट क्यूरियस का जवाब डेड-ऑन है (आपको मेरी बात मिल गई!), और इसका उपयोग हम उस डेटाबेस स्कीमा के संस्करण को ट्रैक करने के लिए करते हैं जो वर्तमान में ऐप में है।

उन माइग्रेशन के माध्यम से चलाने के लिए जो उपयोगकर्ता के लिए प्राप्त करने की आवश्यकता है ऐप के अपेक्षित स्कीमा संस्करण से मेल खाते हुए हम एक स्विच स्टेटमेंट का उपयोग करते हैं। यहाँ एक कट-अप उदाहरण है कि यह हमारे ऐप स्ट्रिप में कैसा दिखता है :

- (void) migrateToSchemaFromVersion:(NSInteger)fromVersion toVersion:(NSInteger)toVersion { 
    // allow migrations to fall thru switch cases to do a complete run
    // start with current version + 1
    [self beginTransaction];
    switch (fromVersion + 1) {
        case 3:
            // change pin type to mode 'pin' for keyboard handling changes
            // removing types from previous schema
            sqlite3_exec(db, "DELETE FROM types;", NULL, NULL, NULL);
            NSLog(@"installing current types");
            [self loadInitialData];
        case 4:
            //adds support for recent view tracking
            sqlite3_exec(db, "ALTER TABLE entries ADD COLUMN touched_at TEXT;", NULL, NULL, NULL);
        case 5:
            {
                sqlite3_exec(db, "ALTER TABLE categories ADD COLUMN image TEXT;", NULL, NULL, NULL);
                sqlite3_exec(db, "ALTER TABLE categories ADD COLUMN entry_count INTEGER;", NULL, NULL, NULL);
                sqlite3_exec(db, "CREATE INDEX IF NOT EXISTS categories_id_idx ON categories(id);", NULL, NULL, NULL);
                sqlite3_exec(db, "CREATE INDEX IF NOT EXISTS categories_name_id ON categories(name);", NULL, NULL, NULL);
                sqlite3_exec(db, "CREATE INDEX IF NOT EXISTS entries_id_idx ON entries(id);", NULL, NULL, NULL);

               // etc...
            }
    }

    [self setSchemaVersion];
    [self endTransaction];
}

1
खैर, मैंने नहीं देखा कि आप toVersionअपने कोड में कहां उपयोग करते हैं? जब आप संस्करण 0 पर हैं तो इसे कैसे संभाला जाता है और उसके बाद दो और संस्करण हैं। इसका मतलब है कि आपको 0 से 1 तक और 1 से 2 तक माइग्रेट करना है। आप इसे कैसे संभालते हैं?
confile

1
@confile में कोई breakस्टेटमेंट नहीं हैं switch, इसलिए बाद के सभी माइग्रेशन भी होंगे।
मैट

स्ट्रिप लिंक मौजूद नहीं है
पेड्रो लूज

20

मुझे FMDB और MBProgressHUD के साथ कुछ माइग्रेशन कोड साझा करने दें।

यहाँ आप स्कीमा संस्करण संख्या पढ़ते और लिखते हैं (यह संभवतः एक मॉडल वर्ग का हिस्सा है, मेरे मामले में यह एक एकल वर्ग है जिसे डेटाबेस कहा जाता है):

- (int)databaseSchemaVersion {
    FMResultSet *resultSet = [[self database] executeQuery:@"PRAGMA user_version"];
    int version = 0;
    if ([resultSet next]) {
        version = [resultSet intForColumnIndex:0];
    }
    return version;
}

- (void)setDatabaseSchemaVersion:(int)version {
    // FMDB cannot execute this query because FMDB tries to use prepared statements
    sqlite3_exec([self database].sqliteHandle, [[NSString stringWithFormat:@"PRAGMA user_version = %d", DatabaseSchemaVersionLatest] UTF8String], NULL, NULL, NULL);
}

यहाँ [self database]विधि है कि lazily डेटाबेस खोलता है:

- (FMDatabase *)database {
    if (!_databaseOpen) {
        _databaseOpen = YES;

        NSString *documentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
        NSString *databaseName = [NSString stringWithFormat:@"userdata.sqlite"];

        _database = [[FMDatabase alloc] initWithPath:[documentsDir stringByAppendingPathComponent:databaseName]];
        _database.logsErrors = YES;

        if (![_database openWithFlags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FILEPROTECTION_COMPLETE]) {
            _database = nil;
        } else {
            NSLog(@"Database schema version is %d", [self databaseSchemaVersion]);
        }
    }
    return _database;
}

और यहां व्यू कंट्रोलर से माइग्रेशन मेथड्स कहलाते हैं:

- (BOOL)databaseNeedsMigration {
    return [self databaseSchemaVersion] < databaseSchemaVersionLatest;
}

- (void)migrateDatabase {
    int version = [self databaseSchemaVersion];
    if (version >= databaseSchemaVersionLatest)
        return;

    NSLog(@"Migrating database schema from version %d to version %d", version, databaseSchemaVersionLatest);

    // ...the actual migration code...
    if (version < 1) {
        [[self database] executeUpdate:@"CREATE TABLE foo (...)"];
    }

    [self setDatabaseSchemaVersion:DatabaseSchemaVersionLatest];
    NSLog(@"Database schema version after migration is %d", [self databaseSchemaVersion]);
}

और यहां रूट व्यू कंट्रोलर कोड है जो माइग्रेशन को आमंत्रित करता है, एक प्रगति bezel प्रदर्शित करने के लिए MBProgressHUD का उपयोग कर रहा है:

- (void)viewDidAppear {
    [super viewDidAppear];
    if ([[Database sharedDatabase] userDatabaseNeedsMigration]) {
        MBProgressHUD *hud = [[MBProgressHUD alloc] initWithView:self.view.window];
        [self.view.window addSubview:hud];
        hud.removeFromSuperViewOnHide = YES;
        hud.graceTime = 0.2;
        hud.minShowTime = 0.5;
        hud.labelText = @"Upgrading data";
        hud.taskInProgress = YES;
        [[UIApplication sharedApplication] beginIgnoringInteractionEvents];

        [hud showAnimated:YES whileExecutingBlock:^{
            [[Database sharedDatabase] migrateUserDatabase];
        } onQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0) completionBlock:^{
            [[UIApplication sharedApplication] endIgnoringInteractionEvents];
        }];
    }
}

नोट: मैं इस बात से पूरी तरह संतुष्ट नहीं हूं कि कोड कैसे व्यवस्थित है (मैं एक ही ऑपरेशन के कुछ हिस्सों के लिए उद्घाटन और माइग्रेशन पसंद करूंगा, अधिमानतः एप प्रतिनिधि द्वारा आमंत्रित), लेकिन यह काम करता है, और मुझे लगा कि मैं फिर भी साझा करूंगा ।
एंड्री टारनटोसोव

आप "user_version" को वापस करने के लिए "setDatabaseSchemaVersion" विधि का उपयोग क्यों करते हैं? "user_version" और "स्कीमा_version" दो अलग-अलग व्यावहारिक उदाहरण हैं जो मुझे लगता है।
पॉल ब्रूसकिनस्की

@PaulBrewczynski क्योंकि मैं आमतौर पर इस्तेमाल की जाने वाली शर्तों को पसंद करता हूं, न कि SQLite शर्तों को, और यह भी कि मैं इसे क्या कह रहा हूं (मेरे डेटाबेस स्कीमा का संस्करण)। मैं इस मामले में SQLite- विशिष्ट शर्तों के बारे में परवाह नहीं है, और schema_versionpragma आम तौर पर कुछ लोगों के साथ या तो सौदा नहीं है।
एंड्री टारंटसोव

आपने लिखा है: // FMDB इस क्वेरी को निष्पादित नहीं कर सकता क्योंकि FMDB तैयार कथनों का उपयोग करने की कोशिश करता है। इससे तुम्हारा क्या मतलब? यह काम करना चाहिए: NSString * क्वेरी = [NSString stringWithFormat: @ "PRAGMA USER_VERSION =% i", userVersion]; [_db executeUpdate: query]; जैसा कि यहां उल्लेख किया गया है: stackoverflow.com/a/21244261/1364174
पॉल ब्रूसकिनस्की

1
(ऊपर मेरी टिप्पणी से संबंधित) नोट: FMDB पुस्तकालय अब सुविधाएँ: userVersion और setUserVersion: विधियाँ! इसलिए आपको वर्बोज़ @Andrey Tarantsov के तरीकों का उपयोग करने की आवश्यकता नहीं है: - (int) databaseSchemaVersion! और (शून्य) setDatabaseSchemaVersion: (int) संस्करण। FMDB प्रलेखन: ccgus.github.io/fmdb/html/Categories/… :
Paul Brewczynski

4

सबसे अच्छा समाधान IMO एक SQLite उन्नयन ढांचे का निर्माण है। मुझे भी यही समस्या थी (C # दुनिया में) और मैंने अपना खुद का ऐसा ढांचा बनाया। आप इसके बारे में यहां पढ़ सकते हैं । यह पूरी तरह से काम करता है और मेरे (पहले बुरे सपने) अपग्रेड काम को मेरी तरफ से कम से कम प्रयास के साथ करता है।

हालाँकि लाइब्रेरी को C # में लागू किया गया है, लेकिन वहाँ प्रस्तुत विचार आपके मामले में भी ठीक काम करने चाहिए।


यह एक अच्छा उपकरण है; बहुत बुरा यह मुफ़्त नहीं है
मिहानी डेमियन

3

1/migrationsSQL- आधारित माइग्रेशन की सूची के साथ फ़ोल्डर बनाएँ , जहाँ प्रत्येक माइग्रेशन कुछ इस तरह दिखता है:

/migrations/001-categories.sql

-- Up
CREATE TABLE Category (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO Category (id, name) VALUES (1, 'Test');

-- Down
DROP TABLE User;

/migrations/002-posts.sql

-- Up
CREATE TABLE Post (id INTEGER PRIMARY KEY, categoryId INTEGER, text TEXT);

-- Down
DROP TABLE Post;

2। उदाहरण के लिए, लागू माइग्रेशन की सूची वाली db तालिका बनाएँ:

CREATE TABLE Migration (name TEXT);

3। एप्लिकेशन बूटस्ट्रैप तर्क को अपडेट करें ताकि शुरू होने से पहले, यह /migrationsफ़ोल्डर से पलायन की सूची को पकड़ लेता है और उन माइग्रेशन को चलाता है जो अभी तक लागू नहीं हुए हैं।

यहाँ जावास्क्रिप्ट के साथ कार्यान्वित एक उदाहरण दिया गया है: Node.js ऐप्स के लिए SQLite क्लाइंट


2

कुछ सुझाव...

1) मैं आपके डेटाबेस को एक NSOperation में माइग्रेट करने और बैकग्राउंड थ्रेड में चलाने के लिए सभी कोड डालने की सलाह देता हूँ। आप डेटाबेस को माइग्रेट किए जाने के दौरान एक स्पिनर के साथ एक कस्टम UIAlertView दिखा सकते हैं।

2) सुनिश्चित करें कि आप अपने डेटाबेस को ऐप के दस्तावेज़ों में बंडल से कॉपी कर रहे हैं और इसे उस स्थान से उपयोग कर रहे हैं, अन्यथा आप बस प्रत्येक ऐप अपडेट के साथ पूरे डेटाबेस को अधिलेखित कर देंगे, और फिर नए खाली डेटाबेस को माइग्रेट करेंगे।

3) FMDB महान है, लेकिन इसका निष्पादन विधि किसी कारण से PRAGMA क्वेरी नहीं कर सकता है। अगर आप PRAGMA user_version का उपयोग करके स्कीमा संस्करण की जांच करना चाहते हैं, तो आपको सीधे अपनी खुद की विधि लिखनी होगी जो sqlite3 का उपयोग करती है।

4) यह कोड संरचना यह सुनिश्चित करेगी कि आपके अपडेट को क्रम में निष्पादित किया जाता है, और यह कि सभी अपडेट निष्पादित किए जाते हैं, भले ही उपयोगकर्ता ऐप अपडेट के बीच कितनी देर तक चले। इसे और अधिक परिष्कृत किया जा सकता है, लेकिन इसे देखने का यह बहुत सरल तरीका है। यह विधि सुरक्षित रूप से हर बार आपके डेटा सिंगलटन को तुरंत चलाया जा सकता है, और केवल एक छोटी db क्वेरी की लागत होती है, जो कि प्रति सत्र केवल एक बार होती है यदि आप अपना डेटा सिंगलटन ठीक से सेट करते हैं।

- (void)upgradeDatabaseIfNeeded {
    if ([self databaseSchemaVersion] < 3)
    {
        if ([self databaseSchemaVersion] < 2)
        {
            if ([self databaseSchemaVersion] < 1)
            {
                // run statements to upgrade from 0 to 1
            }
            // run statements to upgrade from 1 to 2
        }
        // run statements to upgrade from 2 to 3

        // and so on...

        // set this to the latest version number
        [self setDatabaseSchemaVersion:3];
    }
}

1

यदि आप डेटाबेस स्कीमा और सभी कोड को इसे लॉकस्टेप में बदल रहे हैं, जैसा कि एम्बेडेड और फोन-स्थित एप्लिकेशन में होने की संभावना है, समस्या वास्तव में अच्छी तरह से नियंत्रण में है (एक बुरा सपना जो तुलनीय है कि एक पेरिस डीबी पर स्कीमा माइग्रेशन की तुलना में कुछ भी नहीं) हो सकता है कि सैकड़ों ऐप्स सर्व किए जाएं - DBA के नियंत्रण में या तो ;-) नहीं।


0

.Net के लिए आप उपयोग कर सकते हैं lib:

EntityFrameworkCore.Sqlite.Migrations

यह सरल है, इसलिए किसी भी अन्य प्लेटफ़ॉर्म के लिए आप समान व्यवहार को आसानी से लागू कर सकते हैं जैसे कि ब्याज में।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.