# 缓存架构设计
核心原则:`读多写少的数据,缓存起来,减少数据库访问,提升性能`
## 数据表行数缓存
数据表行数缓存`Meta.Count`,`代表了相对准确的表行数,在1000行以内精确`,1000行以上从索引表查询,精确度由数据库保证。
*多年使用经验来看,`SQLite/MSSQL/MySql/Oracle`索引表里面的表行数相当准确*
查询数据表行数一般用`Student.FindCount(where)`,还可以指定条件。
实际使用中,用得最多的是查整表行数,供分页等业务逻辑使用。
`FindCount()`执行的是`Select Count(*) From Table`,在很多没有预热的大数据表(百万千万级)里面,该语句执行起来非常慢,往往需要超过1秒的时间,用户体验很不好。
快速查询`QueryCountFast`从索引表读取表行数。
本框架内,大量使用表行数来决定不同的优化策略。
## 实体缓存
实体缓存就是`整表缓存那些读取很多修改极少的数据`,用于系统参数表、栏目分类表等。
实体类内使用'Meta.Cache.Entities'即可触发使用实体缓存,内部将执行一次查询('Select * From Table')加载整表数据为实体列表。
`Meta.Cache.Entities`就是这个实体列表,使用缓存实际上就是在这个列表上执行`Find`/`FindAll`操作。
基于性能考虑,建议单表数据小于1000行时使用,大于10000时坚决不要使用。
工具生成的实体业务类代码经常可以看到如下代码:
```csharp
public Student FindByID(Int32 id)
{
if (id <= 0) return null;
if (Meta.Count >= 1000)
return Find(__.ID, id);
else // 实体缓存
return Meta.Cache.Entities.Find(__.ID, id);
}
```
因此,`FindByID`在该表数据小于1000时,其实是使用实体缓存。
缓存默认过期时间`60秒`,过期后使用仍然是马上返回旧数据,同时开启异步查询更新缓存。
任何添删改等改动数据库的操作,都将会让缓存马上过期,并启动异步更新。
任何添删改操作,都将实时修改缓存,即使在异步更新完成之前,从缓存拿到的也是最新数据。除非有其它进程更新了数据表,此时需要等缓存的异步更新操作完成才能得到最新数据。
## 单对象缓存
单对象缓存就是`以主键字典逐行缓存数据`,常用于需要单行数据查询的场合,比如用户表等。
可以看做是一个字典,以主键为Key,实体对象为Value。每次查询先在字典里面搜索,找到则返回,找不到则去数据库查,缓存起来后再返回。
实体类内使用`Meta.SingleCache[key]`即可启用单对象缓存。
```csharp
public Student FindByID(Int32 id)
{
if (id <= 0) return null;
return Meta.SingleCache[id];
}
```
缓存默认过期时间`60秒`,过期后更新。
任何添删改等改动数据库的操作,都将会清空缓存。
## 缓存更新策略
在没有使用事务时,对数据表的任何添删改,将会让该表的实体缓存马上过期,以及清空单对象缓存。
使用事务时,每一个添删改操作仅修改缓存,直到事务提交或回滚才清空缓存。
## 特别优化
`SQLite`没有索引表供快速查询表行数,而直接`Select Count`又慢,因此框架针对`SQLite`进行特别优化。
获取表行数时,如果有自增字段,首先获取其最大值临时充当表行数,然后启动异步查询`Select Count`以获取精确的表行数。
因此,`SQLite`使用`Meta.Count`时,第一次得到的数据可能有偏差,一会后即可得到精确数据。时间的长度主要由数据表大小决定,一百万数据大概需要几百毫秒。该偏差完全可以通过系统启动时进行系统预热来对冲掉。
|