使用具有WithoutRowID的主键

时间:2018-04-22 07:25:35

标签: android sqlite android-sqlite

我试图从SQLite中挤出一点点性能,我有一个看似奇怪的问题,因为SQLite中的功能似乎毫无意义。

以例如:

CREATE TABLE "A_TEST" ( "ID" INTEGER PRIMARY KEY ,  "X" TEXT NULL) WITHOUT ROWID

然后尝试插入记录:

Insert into A_TEST (X) VALUES('Test String')

您将收到" NOT NULL约束失败"

的错误

这是否意味着,使用WithoutRowID时,我必须在插入时指定自己的主键值?

我认为WithoutRowID毫无意义的原因是:

  1. 您必须指定自己的主键值,这会使任何质量插入选择语句变得多余,因为我必须在插入时在主键中指定我自己的值....

  2. 如果我不使用WithoutRowID,我实际上会有2个主键,因为SQLite管理自己的RowID以及我自己的主键值。在1.7GB数据库上,使用WithoutRowID可以将文件中的索引大小减少到只有1.3GB,因此400MB的差异可以节省很多。

  3. 请告诉我,我不必提供自己的主键ID,并且如果它是INTEGER,它实际上会为主键提供唯一ID。

5 个答案:

答案 0 :(得分:1)

这是否意味着,在没有WithoutRowID的情况下,我必须在插入时指定自己的主键值?

是的,但是您可以在插入时通过子选择自动生成它:

CREATE TABLE "A_TEST" ( 
    "ID" INTEGER PRIMARY KEY,
    "X" TEXT NULL
) WITHOUT ROWID;

INSERT INTO A_TEST (ID,X) VALUES (
    (SELECT IFNULL(MAX(id),0)) + 1 FROM "A_TEST"),
    'Test String'
);

IFNULL(MAX(id),0)) + 1将为空表返回1,否则将比具有当前最大值的行多一个。

这对于批量插入并不能保持不变,因为所有IFNULL(MAX(id),0)) + 1均被同时求值并且具有相同的值,从而导致唯一约束冲突。但是,您可以生成大量插入语句,以便第一个偏移量为+ 1,第二个偏移量为+ 2,依此类推:

INSERT INTO A_TEST (ID,X) VALUES 
(
    (SELECT IFNULL(MAX(id),0)) + 1 FROM "A_TEST"),
    'Test String 1'
),
(
    (SELECT IFNULL(MAX(id),0)) + 2 FROM "A_TEST"),
    'Test String 2'
);

答案 1 :(得分:0)

不幸的是,它有点像狗吃晚餐: https://www.sqlite.org/rowidtable.html

我引用(如果你不想阅读整个页面): "需要保持流通中数以亿计的SQLite数据库文件的向后兼容性。在一个完美的世界里,不会出现像#rowid"并且所有表都遵循作为WITHOUT ROWID表实现的标准语义,只有没有额外的" WITHOUT ROWID"关键字。不幸的是,生活很混乱。 SQLite的设计师为当前的混乱提供了真诚的道歉。"

答案 2 :(得分:0)

我刚刚发现SQLite 4可用 - 它看起来(从网页的初始读取)主键确实是没有ROWID的REAL主键!呐喊。 http://sqlite.org/src4/doc/trunk/www/design.wiki

答案 3 :(得分:0)

SQLite中有三种表:

  1. WITHOUT ROWID表,存储为按声明的主键排序的B树;
  2. 带有INTEGER PRIMARY KEY的rowid表(其中PK列是内部rowid的别名),它们存储为按声明的主键排序的B树;
  3. 带有任何其他(或没有)主键的rowid表,它们存储为按内部rowid排序的B树。
  4. 1。和2.完全相同,除了你没有用1自动增量。

    因此,如果您想要自动增量,只需删除WITHOUT ROWID(从案例1移至案例2)。 WITHOUT ROWID仅对案例3有所改进,其中主键约束需要单独的索引。

答案 4 :(得分:0)

  

这是否意味着,使用WithoutRowID,我必须指定我自己的Primary   插入时的键值?

是(虽然可能没有下面的一些努力或限制),就好像你尝试: -

    CREATE TABLE IF NOT EXISTS table001 (col1 INTEGER, col2 INTEGER) WITHOUT ROWID;

然后你得到的结论是: -

SQLiteManager: Likely SQL syntax error: CREATE TABLE IF NOT EXISTS table001 (col1 INTEGER, col2 INTEGER) WITHOUT ROWID; [ PRIMARY KEY missing on table table001 ]
Exception Name: NS_ERROR_FAILURE
Exception Message: Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [mozIStorageConnection.createStatement]

所以你需要有一个PRIMARY KEY,而SQLite不会通过指定你告诉SQLite你不想要这个功能的WITHOUT ROWID来为你提供一个值。

将上述内容更改为: -

CREATE TABLE IF NOT EXISTS table001 (col1 INTEGER, col2 INTEGER, PRIMARY KEY (col1,col2)) WITHOUT ROWID;

的工作原理。但是,您需要指定值。

以上对于关联表是有用的,如果主索引是根据您将引用的列而不是  由rowid,它基本上是插入顺序。当然,对于这样的表,如果不知道列的值,您可能不会插入行。

因此可以使用WITHOUT ROWID表。

如果你真的想要你可以很容易地使用'TRIGGER'引入一个自动递增的数字,同时带有一个存储最后使用或下一个数字的行/列的表,但是为什么你会放弃rowid只是为了复制无论如何,rowid为你做了什么。

另一种选择,如果您想要的唯一键是使用CURRENT_TIMESTAMP作为默认值。但是,您无法进行批量插入,因为插入之间的间隔会导致UNIQUE约束冲突。

  

如果我不使用WithoutRowID,我实际上会有2个主键,   因为SQLite管理自己的RowID以及我自己的主键   值。在1.7GB数据库上,拥有WithoutRowID会减小其大小   文件中的索引只有1.3GB,所以400MB差异很大   巨额储蓄。

     

请告诉我,我不必提供自己的主键ID和   它实际上将提供针对主键的唯一ID   是一个INTEGER。

当然,您的“我自己的主键值”并非神奇地生成,即您知道该值(假设它不仅仅是rowid的副本),因此将用于主键。< / p>

有趣的是使用: -

CREATE TABLE IF NOT EXISTS table002 (pk INTEGER PRIMARY KEY, col1 TEXT) 
WITHOUT ROWID;
INSERT INTO table002 VALUES
    (2,'fred'),
    (3,'bert'),
    (-100,'alfred'),
    ('june','mary');

显示使用WITHOUT ROWID可以提高PRIMARY KEY的灵活性,因为它不仅限于INTEGER,而是 rowid ,也不会TEXT PRIMARY KEY..... {1}}

,即上述结果: -

enter image description here

Android的另一个考虑因素

版本3.8.2中引入了

WITHTOUT ROWID,某些设备可能没有安装或更高版本,因此WITHOUT ROWID甚至可能不适用于某些Android应用。

如果你真的想要的例子: -

这是管理你自己的psuedo_autoincrement的一个例子。这不是根据指定的量(本例中为10)递增1增量。它没有完整,因为它没有上限检查/处理。

这是插入3行后的结果: -

enter image description here

这是随附的序列表(准备下一个插入,即_seq 31 ): -

enter image description here

  • _incby是要增加的数量(在这种情况下为10)
  • 每次达到_limit时,
  • _offset列将跳转到1,2 ... 9(允许9个最大值后)。因此,当偏移量为1(在5000次插入之后)时,将使用2,12,22 .....但是,这还没有实施。
  • 我想你可以使用上面的内容来进行一次“自动减少”,并且有点小小的烦恼。

关键(双关语是无意识的)是 TRIGGER : -

CREATE TRIGGER seqtrg_mytable 
AFTER INSERT ON mytable 
BEGIN 
    UPDATE my_sequence SET _seq = _seq + _inc_by 
    WHERE _name = 'mytable';
END

还有 INSERT : -

INSERT INTO mytable VALUES ((SELECT _seq FROM my_sequence) ,'Test001')

使用以下内容在Android上检查/使用: -

DBHelper.java

public class DBHelper extends SQLiteOpenHelper {

    public static final String DBNAME = "weird";
    public static final int DBVERSION = 1;
    public static final String TBMYSEQ = "my_sequence";
    public static final String COL_MYSEQ_NAME = "_name";
    public static final String COL_MYSEQ_SEQ = "_seq";
    public static final String COL_MYSEQ_INCBY = "_inc_by";
    public static final String COL_MYSEQ_OFFSET = "_offset";
    public static final String COl_MYSEQ_LIMIT = "_limit";

    public static final String TBMYTABLE = "mytable";
    public static final String COL_SPECIALIX = "_special_primary_autoinc_index";
    public static final String COL_MYVALUE = "myvalue";
    public static final int MYTABLE_INCYBY = 10;
    public static final int MYTABLE_LIMIT = 50000;

    SQLiteDatabase mDB;

    public DBHelper(Context context) {
        super(context, DBNAME, null, DBVERSION);
        mDB = this.getWritableDatabase();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String crt_myseq_tbl = "CREATE TABLE IF NOT EXISTS " +
                TBMYSEQ + "(" +
                COL_MYSEQ_NAME + " TEXT PRIMARY KEY," +
                COL_MYSEQ_SEQ + " INTEGER DEFAULT 1, " +
                COL_MYSEQ_INCBY + " INTEGER DEFAULT 1, " +
                COL_MYSEQ_OFFSET + " INTEGER DEFAULT 0," +
                COl_MYSEQ_LIMIT + " INTEGER NOT NULL" +
                ")";
        db.execSQL(crt_myseq_tbl);

        String crt_mytable_tbl = "CREATE TABLE IF NOT EXISTS " +
                TBMYTABLE + "(" +
                COL_SPECIALIX + " TEXT PRIMARY KEY DEFAULT -1," +
                COL_MYVALUE + " TEXT" +
                ")";
        db.execSQL(crt_mytable_tbl);
        insertMySeqRow(TBMYTABLE,MYTABLE_INCYBY,MYTABLE_LIMIT,db);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    private void insertMySeqRow(String for_table,int incby, int limit, SQLiteDatabase optionaldb) {
        ContentValues cv = new ContentValues();
        cv.put(COL_MYSEQ_NAME,for_table);
        cv.put(COL_MYSEQ_INCBY,incby);
        cv.put(COl_MYSEQ_LIMIT,limit);
        if (optionaldb == null) {
            optionaldb = mDB;
        }
        optionaldb.insert(TBMYSEQ,null,cv);

        String crt_trigger_sql =
                "CREATE TRIGGER IF NOT EXISTS seqtrg_" + for_table +
                        " AFTER INSERT ON " + for_table +
                        " BEGIN " +
                        " UPDATE " + TBMYSEQ +
                        " SET " + COL_MYSEQ_SEQ + " = " +
                        COL_MYSEQ_SEQ + " + " + COL_MYSEQ_INCBY +
                        " WHERE " + COL_MYSEQ_NAME + " = '" + for_table +
                        "';" +
                        "END";
        optionaldb.execSQL(crt_trigger_sql);
    }

    public void insertMyTableRow(String value) {
        String insertsql = "INSERT INTO " +TBMYTABLE +
                " VALUES (" +
                "(SELECT " + COL_MYSEQ_SEQ + " " +
                "FROM " + TBMYSEQ +
                ") " +
                ",'" + value + "')";
        mDB.execSQL(insertsql);
    }
}

MainActivty.java: -

public class MainActivity extends AppCompatActivity {

    DBHelper mDBHlpr;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDBHlpr = new DBHelper(this);
        mDBHlpr.insertMyTableRow("Test001");
        mDBHlpr.insertMyTableRow("Test002");
        mDBHlpr.insertMyTableRow("TEST003");
        Cursor csr1 = CommonSQLiteUtilities.getAllRowsFromTable(
                mDBHlpr.getWritableDatabase(),
                DBHelper.TBMYTABLE,
                true,
                null
        );
        CommonSQLiteUtilities.logCursorData(csr1);
        csr1.close();
        Cursor csr2 = CommonSQLiteUtilities.getAllRowsFromTable(
                mDBHlpr.getWritableDatabase(),
                DBHelper.TBMYSEQ,
                true,
                null
        );
        CommonSQLiteUtilities.logCursorData(csr2);
        csr2.close();
    }
}

输出是: -

04-22 11:57:53.120 2910-2910/? D/SQLITE_CSU: DatabaseList Row 1 Name=main File=/data/data/weird.weirdprimarykey/databases/weird
    PRAGMA -  sqlite_version = 3.7.11
    PRAGMA -  user_version = 1
04-22 11:57:53.128 2910-2910/? D/SQLITE_CSU: PRAGMA -  encoding = UTF-8
    PRAGMA -  auto_vacuum = 1
    PRAGMA -  cache_size = 2000
    PRAGMA -  foreign_keys = 0
    PRAGMA -  freelist_count = 0
    PRAGMA -  ignore_check_constraints = 0
04-22 11:57:53.132 2910-2910/? D/SQLITE_CSU: PRAGMA -  journal_mode = persist
    PRAGMA -  journal_size_limit = 524288
    PRAGMA -  locking_mode = normal
    PRAGMA -  max_page_count = 1073741823
    PRAGMA -  page_count = 7
    PRAGMA -  page_size = 4096
    PRAGMA -  recursive_triggers = 0
    PRAGMA -  reverse_unordered_selects = 0
    PRAGMA -  secure_delete = 0
04-22 11:57:53.136 2910-2910/? D/SQLITE_CSU: PRAGMA -  synchronous = 2
    PRAGMA -  temp_store = 0
    PRAGMA -  wal_autocheckpoint = 100


    Table Name = android_metadata Created Using = CREATE TABLE android_metadata (locale TEXT)
    Table = android_metadata ColumnName = locale ColumnType = TEXT Default Value = null PRIMARY KEY SEQUENCE = 0
    Number of Indexes = 0
    Number of Foreign Keys = 0
    Number of Triggers = 0
    Table Name = my_sequence Created Using = CREATE TABLE my_sequence(_name TEXT PRIMARY KEY,_seq INTEGER DEFAULT 1, _inc_by INTEGER DEFAULT 1, _offset INTEGER DEFAULT 0,_limit INTEGER NOT NULL)
    Table = my_sequence ColumnName = _name ColumnType = TEXT Default Value = null PRIMARY KEY SEQUENCE = 1
    Table = my_sequence ColumnName = _seq ColumnType = INTEGER Default Value = 1 PRIMARY KEY SEQUENCE = 0
    Table = my_sequence ColumnName = _inc_by ColumnType = INTEGER Default Value = 1 PRIMARY KEY SEQUENCE = 0
    Table = my_sequence ColumnName = _offset ColumnType = INTEGER Default Value = 0 PRIMARY KEY SEQUENCE = 0
    Table = my_sequence ColumnName = _limit ColumnType = INTEGER Default Value = null PRIMARY KEY SEQUENCE = 0
    Number of Indexes = 1
    INDEX NAME = sqlite_autoindex_my_sequence_1
        Sequence = 0
        Unique   = true
        Index Origin indicator unsupported
        Index Partial indicator unsupported
        INDEX COLUMN = _name COLUMN ID = 0 SEQUENCE = 0
    Number of Foreign Keys = 0
    Number of Triggers = 0
    Table Name = mytable Created Using = CREATE TABLE mytable(_special_primary_autoinc_index TEXT PRIMARY KEY DEFAULT -1,myvalue TEXT)
04-22 11:57:53.140 2910-2910/? D/SQLITE_CSU: Table = mytable ColumnName = _special_primary_autoinc_index ColumnType = TEXT Default Value = -1 PRIMARY KEY SEQUENCE = 1
    Table = mytable ColumnName = myvalue ColumnType = TEXT Default Value = null PRIMARY KEY SEQUENCE = 0
    Number of Indexes = 1
    INDEX NAME = sqlite_autoindex_mytable_1
        Sequence = 0
        Unique   = true
        Index Origin indicator unsupported
        Index Partial indicator unsupported
        INDEX COLUMN = _special_primary_autoinc_index COLUMN ID = 0 SEQUENCE = 0
    Number of Foreign Keys = 0
    Number of Triggers = 1
        TRIGGER NAME =seqtrg_mytable
        SQL = CREATE TRIGGER seqtrg_mytable AFTER INSERT ON mytable BEGIN  UPDATE my_sequence SET _seq = _seq + _inc_by WHERE _name = 'mytable';END


    Cursor has 3 rows and 2 Columns.
    Information for Row 1 offset = 0
        For Column _special_primary_autoinc_indexType is STRING value as String is 1 value as long is 1 value as double is 1.0
        For Column myvalueType is STRING value as String is Test001 value as long is 0 value as double is 0.0
    Information for Row 2 offset = 1
        For Column _special_primary_autoinc_indexType is STRING value as String is 11 value as long is 11 value as double is 11.0
        For Column myvalueType is STRING value as String is Test002 value as long is 0 value as double is 0.0
    Information for Row 3 offset = 2
        For Column _special_primary_autoinc_indexType is STRING value as String is 21 value as long is 21 value as double is 21.0
        For Column myvalueType is STRING value as String is TEST003 value as long is 0 value as double is 0.0
    Cursor has 1 rows and 5 Columns.
    Information for Row 1 offset = 0
        For Column _nameType is STRING value as String is mytable value as long is 0 value as double is 0.0
        For Column _seqType is INTEGER value as String is 31 value as long is 31 value as double is 31.0
        For Column _inc_byType is INTEGER value as String is 10 value as long is 10 value as double is 10.0
        For Column _offsetType is INTEGER value as String is 0 value as long is 0 value as double is 0.0
        For Column _limitType is INTEGER value as String is 50000 value as long is 50000 value as double is 50000.0