数据库的主要瓶颈是磁盘 I/O。为了提高性能,现代数据库使用缓存管理器。
查询执行器不会直接从文件系统拿数据,而是向缓存管理器要。缓存管理器有一个内存缓存区,叫做缓冲池,从内存读取数据显著地提升数据库性能。对此很难给出一个数量级,因为这取决于你需要的是哪种操作:
但是这导致了另一个问题 缓存管理器需要在查询执行器使用数据之前得到数据不然的话查询管理器不得不等待数据从缓慢的磁盘中读出来
然后这个步骤是可优化的
可以采用预读
缓存管理器在缓冲池里保存所有的这些数据。为了确定一条数据是否有用,缓存管理器给缓存的数据添加了额外的信息(叫闩锁)。
什么叫一条数据可用 难道载入内存的数据 还会在载入时删掉 这怎么有点像内存中的写缓冲器
但是问题由来了 有的时候查询执行器不知道它需要什么数据
数据库使用一种推测预读法(比如:如果查询执行器想要数据1、3、5,它不久后很可能会要 7、9、11),或者顺序预读法(这时候缓存管理器只是读取一批数据后简单地从磁盘加载下一批连续数据)。
又可以优化了。。
为了监控预读的工作状况,现代数据库引入了一个度量叫缓冲/缓存命中率,用来显示请求的数据在缓存中找到而不是从磁盘读取的频率。
但是问题又来了
如果有经常执行的查询,但是我们知道缓冲是容量有限的内存空间
那么每次都把查询结果加载然后清除,效率就太低了。现代数据库用缓冲区置换策略来解决这个问题。
LRU代表最近最少使用(Least Recently Used)算法,背后的原理是:在缓存里保留的数据是最近使用的,所以更有可能再次使用。
和LinkedHashMap有一曲同工之妙
Map<String, Employee〉staff = new LinkedHashMap<>();
staff.put("144-25-5464", new Employee("Amy Lee"));
staff.put("567-24-2546", new Employee("Harry Hacker"));
staff.put("157-62-7935", new Employee("Gary Cooper"));
staff.put("456-62-5527", new Employee("Francesca Cruz"));//使用无参构造器遍历的结果是 将按照插入顺序对映射条目进行迭代 如果后续插入元素 迭代的顺序不会改变化
LinkedHashMap(K, V>(initialCapacity, loadFactor, true);
//将按照访问顺序对映射条目进行迭代 每次调用get和put 受到影响的条目将从当前的位置删除,并放到条目列表的尾部 只有该条目在链表中的位置会受到影响 而散列表中的桶不会受到影响
这么好的算法也有问题的
如果对一个大表执行全表扫描怎么办?换句话说,当表/索引的大小超出缓冲区会发生什么?使用这个算法会清除之前缓存内所有的数据,而且全扫描的数据很可能只使用一次。
为了防止这个现象,有些数据库增加了特殊的规则,比如Oracle文档中的描述:
『对非常大的表来说,数据库通常使用直接路径来读取,即直接加载区块[……],来避免填满缓冲区。对于中等大小的表,数据库可以使用直接读取或缓存读取。如果选择缓存读取,数据库把区块置于LRU的尾部,防止清空当前缓冲区。』
还有更牛逼的算法
使用高级版本的LRU,叫做 LRU-K。例如,SQL Server 使用 LRU-2。
这个算法的原理是把更多的历史记录考虑进来。简单LRU(也就是 LRU-1),只考虑最后一次使用的数据。
什么叫只考虑最后一次使用的数据
就是直接操作 比如上述的LinkedHashMap
LRU-K呢:
一个ACID事务是一个工作单元 它要保证4个属性
SQL定义了4个隔离级别
简而言之 对添加可见 其他不可见
读取已提交
可重复读+新的隔离突破。如果事务A读取了数据D,然后数据D被事务B修改(或删除)并提交,事务A再次读取数据D时数据的变化(或删除)是可见的。
读取未提交
读取未提交(Read uncommitted):最低级别的隔离,是读取已提交+新的隔离突破。如果事务A读取了数据D,然后数据D被事务B修改(但并未提交,事务B仍在运行中),事务A再次读取数据D时,数据修改是可见的。如果事务B回滚,那么事务A第二次读取的数据D是无意义的,因为那是事务B所做的从未发生的修改(已经回滚了嘛)。
这叫脏读(dirty read)。
本文发布于:2024-02-05 05:15:25,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170724985363356.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |