有几个问题。
一是粒度。您的应用程序可以在数据库之上进行非常精细的缓存。例如,数据库可能只缓存数据页面,而不必缓存特定的行。
另一件事是,应用程序可以以“本机”格式存储数据,而数据库显然仅以其内部格式进行缓存。
简单的例子。
假设你有一个数据库,它是由列的用户:USERID
,FIRSTNAME
,LASTNAME
。非常简单。
您希望将User加载USERID=123
到您的应用程序中。涉及哪些步骤?
- 发出数据库调用
- 解析请求(
SELECT * FROM USER WHERE USERID = ?
)
- 计划请求(即系统将如何获取数据)
- 从磁盘中获取数据
- 将数据从数据库流传输到应用程序
- 将数据库数据转换为应用程序数据(即,
USERID
转换为整数,例如,将名称转换为字符串)。
数据库缓存可能会缓存步骤2和3(这是语句缓存,因此它不会解析或重新计划查询),并缓存实际的磁盘块。
所以,这是关键。用户USER ID 123
名JESSE JAMES
。您可以看到这不是很多数据。但是数据库正在缓存磁盘块。您有索引块(带有123
),然后是数据块(具有实际数据,以及适合该块的所有其他行)。因此,名义上说60-70字节的数据实际上对4K-16K的DB具有缓存和数据影响(取决于块大小)。
光明的一面?如果您需要附近的另一排(例如USER ID = 124
),则索引的可能性很高,并且已经缓存了数据。
但是,即使有了这种缓存,您仍然必须支付在数据线上移动数据的费用(除非您使用本地数据库,否则它总是在数据线上移动,这就是环回),并且您在“解组”数据。即,将其从数据库位转换为语言位,再转换为应用位。
现在,一旦Application获得其USER ID 123
,它将值填充在一个长期存在的哈希图中。
如果应用程序再次需要它,它将在本地地图,应用程序缓存中查找并节省查找,电汇和编组成本。
应用程序缓存的阴暗面是同步。如果有人进来并执行了UPDATE USER SET LASTNAME="SMITH" WHERE USERID=123
,则您的应用程序不会“知道”,因此缓存很脏。
因此,在处理这种关系方面有很多细节,可以使应用程序与数据库保持同步。
拥有大量的数据库缓存非常适合对“热”数据集进行大型查询。您拥有的内存越多,您可以拥有的“热”数据就越多。如果可以将整个数据库缓存在RAM中,那么就可以消除将数据从磁盘移动到RAM缓冲区的I / O(至少对于读取而言)延迟。但是您仍然要承担运输和编组费用。
应用程序可以更具选择性,例如,缓存更多有限的数据子集(DB仅是缓存块),并且使数据与应用程序“更近”可以提高性能。
不利的一面是,并非所有内容都缓存在应用程序中。与应用程序相比,数据库总体上倾向于更有效地存储数据。您还缺少针对应用程序缓存数据的“查询”语言。大多数人只是通过一个简单的密钥进行缓存,然后从那里去。容易找到USER ID 123
,而“所有用过JESSE的用户”则更难。
数据库缓存往往是“免费的”,您可以设置一个缓冲区号,而DBMS则处理其余的。低影响,减少总体I / O和磁盘延迟。
应用程序缓存是特定于应用程序的。
它对于隔离的“静态”数据非常有效。那很容易。在启动时将大量内容加载到查找表中,如果它们发生更改,则重新启动应用程序。这很容易做到。
之后,随着您添加“脏”逻辑等,复杂性开始增加。
归结为,只要拥有Data API,就可以增量缓存。
因此,只要您getUser(123)
到处调用而不是访问数据库,那么以后您就可以返回并添加缓存而getUser
不会影响您的代码。
因此,我总是在每个人的代码中建议使用某种数据访问层,以提供抽象和拦截层。