为什么要用数据库
iOS端持久化的方案选择比较多,NSUserDefault, Keychain, File, sqlite都可以帮助存储关键的业务数据.NSUserDefault和Keychain都是轻量级解决方案,自定义数据格式的File则读取麻烦一些,每次更新部分数据都会导致整个文件io,数据的结构一旦复杂起来,最后还是会走向sqlite.
sqlite是移动端的轻量级数据库解决方案,它的应用之广几乎已经遍及我们日常生活当中被所使用的主流app,大部分人所熟知的CoreData或者FMDB,其内核都是基于sqlite.现在第三方的封装使的sqlite的使用更为便捷,但数据库是计算机科学一大知识体系,其涵盖的知识点相当庞大,简单用起来很简单,用的合理用的溜就不那么容易了.
Relation(关系) vs Object(对象)
其实对于sqlite这类关系型数据库,在数据的存储和表现形式上和面向对象当中的object还是存在很大的差异的.我们平时常使用的OOP编程语言的时候,习惯性思维会对象去模拟,描述一切合业务相关的存在,比如用户,商品,购物车,浏览记录,购买记录等等,这些可以方便的对应到一个个的table,但Object在描述对象的时候更加灵活,比如UserProfile对象,他可以有一个property来描述他的朋友列表.
@interface UserProfile: NSObject
@property (nonatomic, strong) NSArray * friends;
@end
可以用Array这类集合的概念进一步细化表示Object,但sqlite的table只能存储scalar type, 也就是单一数据类型,无法存储Array,关系型数据库的做法通常是通过主键和外键,在两个表之间来表示关系.当然我们也可以在UserProfile表中增加一个自定义的blobdata或者格式化后的特殊String来存储array, 但这种设计已经脱离关系数据库的范畴了.
总结: sqlite这类关系型数据库更加强调关系,将内存中的OOP对象保持至数据库的时候需要进一步转化的工作,将OOP的Relation转化为sqlite的Relation.
index(索引)
索引是平常数据库使用当中基础中的基础,如果只是将数据转化为表进行保持,下次用时再取,在表记录变得庞大以后很容易出现性能问题.用数据库保持数据的另一大好处是数据的读取可以很快,和传统的文件存储相比,性能不是一个量级的.当然我们需要索引的帮助,index可以让我们以特定的方式读取或查找某些记录.
所谓的建索引是给原数据表新建了一个index表来方便查找,如果我们给User表中的Name做了索引,当我们根据Name去sql查询的时候,第一步其实是去User表对应的Index表去做查询,Index表以Name为key建立了一个B+Tree的树形结构.
磁盘I/O瓶颈
内存(Memory)较之于磁盘(Disk),读取速度快,但由于价格贵所以空间比Disk小,也正式由于这个原因导致我们大尺寸的数据都只能存储在Disk上,用的时候再去内存取,每读一次就触发一次磁盘I/O,同理我们的sqlite其实说白了就是一个xxx.db文件,每次去做sql查询的时候就要去读文件,大多数一次查询往往无法通过一次I/O完成,所以如何减少磁盘I/O成为我们优化sqlite性能的关键指标.
Random Access和Sequential Access。Random Access是指我们访问的地址是随机分布的,当前需要读取0x00000001,下一刻可能读取0x000A0001。而Sequential Access则是严格按照顺序寻址的,0x00000001下一刻跟的是0x00000002。对于内存来说,Random Access和Sequential Access在性能上没有任何差异。对于Disk,Sequential Access也能很好的适应,磁盘的机械旋转就能顺利的读到连续的地址,Random Access就比较费时了,可能需要磁头和磁盘的多次机械运动才能重新定位到目标地址。
磁盘的读取方式
磁盘读取数据的时候都是以Page为单位,页(page)的概念很重要,Page是计算机存储时所使用的基础逻辑单位,内存和磁盘当中的数据存储和交互都是以页为单位.即使内存只需要1个字节的数据,从磁盘读取的时候也是拿到一个或多个page,这是一种常用的预先缓存策略.
table记录的存储方式
User表中,ID(Int,4 Bytes),,Name(String,128 Bytes),Gender(Int,4 Bytes),Address(String,128 Bytes),所以一行记录所占的空间就是4+128+4+128=264 Bytes。假设一个Page大小为4 KB,那么一页我们可以存储4*1024/264≈15条记录,也就是说我们一次I/O我们可以获取到User表15调记录.
索引并不是越多越好
虽然索引能加快查询的速度,但同时增加了额外的一个表来存储B+Tree结构的数据,1million条记录就对应一个1million条记录的Index表,额外开销非常可观。所以我们平常应该只给必要的字段(有被查询需求)建索引,而且索引还会增加insert和delete的时间复杂度.
给数值类型建索引会比String类型建索引,效率更好
其实更合理的表述应该是,建立Index的字段的Data Type大小越小,我们索引查询的性能就越高。原因很简单,数据越小,单条记录的磁盘开销就越小,一个Page所包含的记录数量也就越多,这样我们磁盘I/O的时候自然命中率就越高。这也是为什么我们总是给ID建索引,而很少对Name建索引。当然这种性能的差异只有在表记录非常庞大的时候才能看出差别.
Sqite基础知识
- 文件分析
- MyDB.db是各个tables存储的位置
- wal是sqlite的日志文件,全称是write-ahead log
- MyDB.db-shm文件是用来辅佐-wal文件的,shm是shared memory的缩写,可以看做是wal文件的一个index文件,是为了辅助sqlite快速定位wal文件信息.
- 使用命令行分享sqlite.db文件
打开db
sqlite3 MyDB.db
展示db文件中的tables
.tables
展示某个table的字段构成(schema)
.schema tableName
执行sql语句
select * from tableName where ...;
展示结果的时候显示顶部column名称
.head on
网友评论