【问题标题】:Google I/O 2010 About SQLite TransactionsGoogle I/O 2010 关于 SQLite 事务
【发布时间】:2014-10-06 06:27:58
【问题描述】:

由于这是一个非常丰富的会议,我再次观看它,在 24:17 分钟我注意到 Virgil 说

此外,在使用 SQLite 时使用事务,它们不仅可以保持数据完整性,还可以提高数据库操作的性能

编辑:他的“使用交易”到底是什么意思,他的意思是告诉我们使用BEGIN TRANSACTION 语句,还是他提到了别的东西?

如果是第一个,那么:

  1. 这是否意味着我们应该使用SQLiteDatabase#rawQuery() 方法而不是提供的SQLiteDatabase#query() 方法来编写原始SQL 语句?

  2. 它与使用SELECT语句和TRANSACTION语句有什么区别?

【问题讨论】:

  • 您能详细说明您的问题吗?您是否在问如何使用事务、数据完整性意味着什么或事务如何提高性能?你引用的陈述很清楚。
  • @323go 你说得对,我太兴奋了,忘了解释我不完全理解的部分。已修改
  • 您可以只使用带有常规query() 语句的显式事务。 db.beginTransaction()db.setTransactionSuccessful()db.endTransaction() 是您的朋友。不要忘记使用try/catch 进行包装,并始终以finally 结束事务。
  • 我可以得到一个代码示例吗?我一直在使用 SQLite 开发很多应用程序,并且以前从未使用过它,所以如果可能的话,我想更好地了解它们
  • 我在下面提供了一个骨架模式。可能比从其他地方复制整个项目更有帮助;)

标签: android googleio


【解决方案1】:

简单的例子来解释你需要数据库事务和使用准备好的语句等。

在插入大量记录时,即数千条左右的记录,我们遇到了“插入速度”的问题。 Android中常用的insert命令比较慢,所以我们可以使用事务和prepared statement。

在我们的例子中,我们在插入查询中使用 INSERT OR REPLACE INTO,因为我们想根据创建的触发器 (INDEX) 更新已经存在的行。

如果您使用 INSERT OR REPLACE INTO 命令,则必须创建触发器。此 SQL 触发器在表创建后执行(参见下面的 DatabaseHandler.java)

另一个加快插入速度的重要因素是使用准备好的语句。

您可以在下面找到示例:

MainActivity.java – 包含 AsyncTask,当用户单击按钮时将执行该任务以向 db 插入大量数据。

public class MainActivity extends Activity {


final String TAG = "MainActivity.java";
EditText editTextRecordNum;
TextView tvStatus;

@Override
protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    View.OnClickListener handler = new View.OnClickListener() {
        public void onClick(View v) {

            switch (v.getId()) {

            case R.id.buttonNormalInsert:
                new AsyncInsertData("normal").execute();
                break;
            case R.id.buttonFastInsert:
                new AsyncInsertData("fast").execute();
                break;
            }
        }
    };

    // EditText for entering desired number of records to be inserted
    editTextRecordNum = (EditText) findViewById(R.id.editTextRecordNum);

    // Button for normal and fast insert
    findViewById(R.id.buttonNormalInsert).setOnClickListener(handler);
    findViewById(R.id.buttonFastInsert).setOnClickListener(handler);

    // status TextView
    tvStatus = (TextView) findViewById(R.id.textViewStatus);

}

// we used AsyncTask so it won't block the UI thread during inserts.
class AsyncInsertData extends AsyncTask<String, String, String> {

    DatabaseHandler databaseHandler;
    String type;
    long timeElapsed;

    protected AsyncInsertData(String type){
        this.type  = type;
        this.databaseHandler = new DatabaseHandler(MainActivity.this);
    }

    // @type - can be 'normal' or 'fast'
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        tvStatus.setText("Inserting " + editTextRecordNum.getText() + " records...");
    }

    @Override
    protected String doInBackground(String... aurl) {

        try {

            // get number of records to be inserted
            int insertCount = Integer.parseInt(editTextRecordNum.getText().toString());

            // empty the table
            databaseHandler.deleteRecords();

            // keep track of execution time
            long lStartTime = System.nanoTime();

            if (type.equals("normal")) {
                databaseHandler.insertNormal(insertCount);
            } else {
                databaseHandler.insertFast(insertCount);
            }

            // execution finised
            long lEndTime = System.nanoTime();

            // display execution time
            timeElapsed = lEndTime - lStartTime;

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    protected void onPostExecute(String unused) {
        tvStatus.setText("Done inserting " + databaseHandler.countRecords() + " records. Time elapsed: " + timeElapsed / 1000000 + " ms."); 
    }

}

}

DatabaseHandler.java – 处理数据库操作,例如创建表、清空数据库、统计数据库记录和使用循环插入数据。

public class DatabaseHandler extends SQLiteOpenHelper {


// for our logs
public static final String TAG = "DatabaseHandler.java";

// database version
private static final int DATABASE_VERSION = 7;

// database name
protected static final String DATABASE_NAME = "DatabaseName";

// table details
public String tableName = "locations";
public String fieldObjectId = "id";
public String fieldObjectName = "name";
public String fieldObjectDescription = "description";

// constructor
public DatabaseHandler(Context context) {
    super(context, DATABASE_NAME, null, DATABASE_VERSION);
}

// creating table
@Override
public void onCreate(SQLiteDatabase db) {

    String sql = "";

    sql += "CREATE TABLE " + tableName;
    sql += " ( ";
    sql += fieldObjectId + " INTEGER PRIMARY KEY AUTOINCREMENT, ";
    sql += fieldObjectName + " TEXT, ";
    sql += fieldObjectDescription + " TEXT ";
    sql += " ) ";

    db.execSQL(sql);

    // create the index for our INSERT OR REPLACE INTO statement.
    // this acts as the WHERE name="name input" AND description="description input"
    // if that WHERE clause is true, I mean, it finds the same name and description in the database,
    // it will be REPLACEd. 
    // ELSE, what's in the database will remain and the input will be INSERTed (new record)
    String INDEX = "CREATE UNIQUE INDEX locations_index ON "
                    + tableName + " (name, description)";

    db.execSQL(INDEX);
}


// When upgrading the database, it will drop the current table and recreate.
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    String sql = "DROP TABLE IF EXISTS " + tableName;
    db.execSQL(sql);

    onCreate(db);
}

// insert data using transaction and prepared statement
public void insertFast(int insertCount) {

    // you can use INSERT only
    String sql = "INSERT OR REPLACE INTO " + tableName + " ( name, description ) VALUES ( ?, ? )";

    SQLiteDatabase db = this.getWritableDatabase();

    /*
     * According to the docs http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html
     * Writers should use beginTransactionNonExclusive() or beginTransactionWithListenerNonExclusive(SQLiteTransactionListener) 
     * to start a transaction. Non-exclusive mode allows database file to be in readable by other threads executing queries.
     */
    db.beginTransactionNonExclusive();
    // db.beginTransaction();

    SQLiteStatement stmt = db.compileStatement(sql);

    for(int x=1; x<=insertCount; x++){

        stmt.bindString(1, "Name # " + x);
        stmt.bindString(2, "Description # " + x);

        stmt.execute();
        stmt.clearBindings();

    }

    db.setTransactionSuccessful();
    db.endTransaction();

    db.close();
}

// inserts the record without using transaction and prepare statement
public void insertNormal(int insertCount){
    try{

        SQLiteDatabase db = this.getWritableDatabase();

        for(int x=1; x<=insertCount; x++){

            ContentValues values = new ContentValues();
            values.put(fieldObjectName, "Name # " + x);
            values.put(fieldObjectDescription, "Description # " + x);

            db.insert(tableName, null, values);

        }

        db.close();

    }catch(Exception e){
        e.printStackTrace();
    } 
}

// deletes all records
public void deleteRecords(){

    SQLiteDatabase db = this.getWritableDatabase();
    db.execSQL("delete from "+ tableName);
    db.close();
}

// count records
public int countRecords(){

    SQLiteDatabase db = this.getWritableDatabase();

    Cursor cursor = db.rawQuery("SELECT count(*) from " + tableName, null);
    cursor.moveToFirst();

    int recCount = cursor.getInt(0);

    cursor.close();
    db.close();

    return recCount;
}

}

activity_main.xml – 布局,以便我们可以输入要插入的所需记录数,选择我们希望它是“正常”还是“快速”插入,以及操作的状态。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >

<EditText
    android:id="@+id/editTextRecordNum"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:inputType="number"
    android:singleLine="true"
    android:ems="10" >

    <requestFocus />
</EditText>

<Button
    android:id="@+id/buttonNormalInsert"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignLeft="@+id/editTextRecordNum"
    android:layout_below="@+id/editTextRecordNum"
    android:text="Normal Insert" />

<Button
    android:id="@+id/buttonFastInsert"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignBaseline="@+id/buttonNormalInsert"
    android:layout_alignBottom="@+id/buttonNormalInsert"
    android:layout_toRightOf="@+id/buttonNormalInsert"
    android:text="Fast Insert" />

<TextView
    android:id="@+id/textViewStatus"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignLeft="@+id/buttonNormalInsert"
    android:layout_below="@+id/buttonNormalInsert"
    android:padding="10dp"
    android:text="Status" />

【讨论】:

    【解决方案2】:

    根据SQLITE 文档:

    事务数据库是一个包含所有更改和查询的数据库 似乎是原子的、一致的、孤立的和持久的 (ACID)。 SQLite 实现原子的、一致的、可序列化的事务, 隔离且持久,即使事务被 程序崩溃、操作系统崩溃或电源故障 电脑。

    在android中通常插入很慢,所以当你想插入大量数据时,你必须使用事务。 当您要向数据库中插入数千条记录时,插入每条记录需要花费大量时间和宝贵资源。在这种情况下,批量插入或更新可以加快进程。

    这里是一个如何在android中使用事务的例子:

    database.beginTransaction();
    // or use  use beginTransactionNonExclusive() or beginTransactionWithListenerNonExclusive(SQLiteTransactionListener)
    // to start a transaction. Non-exclusive mode allows database file to be in readable by other threads executing queries.
    database.beginTransactionNonExclusive();
        try {
            String sql = "Insert or Replace into Students (student_number, age, phone) values(?,?,?)";
            SQLiteStatement compileStatement = database.compileStatement(sql);
            for(int i = 0; i < studentList.size(); i++) {
                compileStatement.bindString(1, studentList.get(i).student_numerb());
                compileStatement.bindString(2, studentList.get(i).age());
                compileStatement.bindString(3, studentList.get(i).phone());
                compileStatement.execute();
                database.setTransactionSuccessful();
            } catch(Exception e){
                e.printStackTrace();
            } finally {
                database.endTransaction();
            }
    

    【讨论】:

    • 您不需要在catch 块中结束事务,因为您已经在finally 中结束了它。这个块总是执行,不管异常(首先处理)。
    猜你喜欢
    • 2017-09-08
    • 2021-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多