MySQL基本知识总结

innodb 存储引擎

redolog

innodb的数据先存在内存中再到磁盘,在内存刷到磁盘的过程中,出现故障可能导致数据丢失,为此设计了redolog用于保障数据完整,保证redolog记录在落盘前,故障后通过redolog恢复还未持久化的数据。
redolog维护了一份写盘数据,同时为了保证数据完整,事务提交必须在redolog写盘后执行。从而redolog落盘的操作影响性能,所以必须保证redolog数据量尽量小。另外redolog的操作必须保证幂等,为了多次执行也能达到一致的效果。为了提高性能,redolog操作修改应该只对应同一个磁盘页空间Page,避免切换多个Page操作的性能消耗。
一般日志会分为逻辑日志和物理日志。逻辑日志的特点是幂等,物理日志的特点是数据量小。为了同时兼具整两种特点,redolog设计为Physiological Logging的方式,同时具备两种日志类型的优势。同时redolog有多种类型结构。redolog日志文件通过初始化创建,并且首位相连循环使用(ib_file0、ib_file1)。每个文件以block为单位划分。一条redo记录可能被划分到不同的block中,通过lsn和sn去记录偏移标识。file文件中前4个block用于记录checkpoint及文件中block块header信息。
对于事务数据的操作,redolog的写入,先写入logbuffer,让后刷新pagecache,在落盘,之后才提交事务。
而对于一次事务提交,可能涉及多个redolog,这些redolog可能是同一个page的也可能是多个page的,那么需要保证同一个事务的多个redolog原子性。innodb中通过mtr实现,将多个redolog合并为一个相当于简单的事务记录,记录到mtr_log,事务提交后,再刷新到logbuffer中。而对于高并发场景多个mtr同时操作,为了保证数据写入logbuffer是线程安全,每个mtr都会原子的对全局偏移量做增加,从而保证分配到拷贝数据所需要的空间线程安全。
对于logbuffer中redolog的落盘,因为多个mtr同时操作,此时的lsn位置的数据可能还未复制到logbuffer中,导致部分数据空洞。innodb中通过link_buffer的机制解决这个问题。相当于会记录当前连续的logbuffer偏移量,然后循环判断连续点到lsn的位置是否完成数据写入,完成则写入系统Pagecache。记录写入偏移量。之后通知log_flusher线程fsync刷盘。

redo的重放过程,file文件大小有限,为了减少redo的重放次数。innodb通过线程记录checkpoint,用于标记上次完成的刷盘位置。redo重放只需要从最新的checkpoint位置开启,从而复用checkpoint之前的文件空间。
checkpoint必须保证checkpoint前的所有redo记录都已经刷盘。buffer pool中有两个变量lwm_lsn、dpa_lsn。分别记录所有脏页中redolog最小LSN偏移量、已经刷新到bufferpool的最小LSN偏移量。中如果使用lwm_lsn记录checkpoint,由于前面提到的mtr并发场景下,部分数据还未刷新到bufferpool中,所以会取两者中较小的作为checkpoint点,以保证checkpoint之前的redo都已经刷脏到磁盘。

buffer pool

buffer pool的存在是为了解决内存与磁盘读写性能不一致的方案。数据操作先通过在buffer pool中操作,而不是直接与磁盘打交道,提高数据库交互的性能。
数据库启动即占用内存空间,直到数据库关闭才释放。buffer pool中按innodb_buffer_pool_size/innodb_buffer_pool_instances分为多个实例。当innodb_buffer_pool_size<1G是实例数为1,为了避免过多小的实例影响性能。
buffer pool实例由buffer pool chunk组成,buffer pool chunk是实例中连续的物理单元。默认128m。每个数据页在chunk中的存储都包含一个控制体信息。控制体信息包括space_no,page_no,page的地址。
page hash:用于记录页的数据,由于buffer pool值的LRU/FLU/FREE list都是链表的结构,当需要访问页数据是通过space_id\page_no能快速定位页位置。

  • buffer pool中的链表
    Free List:用于记录buffer pool那些页是空闲的,可以使用的,当sql执行需要分配新的数据页,则从free list申请,如果无法申请新的数据页,则会从LRU或FLU list中淘汰节点。
    LRU List:比较常用的淘汰策略,将常用的数据放头部,不常使用的数据放尾部,淘汰时淘汰尾部数据。其中链表右分为热数据区和冷数据区,新的page数据先加入到冷数据区。
    Flush List:用与记录脏页数据。buffer pool中既存在脏页数据页存在干净页数据,对于内存与磁盘不一致的脏页数据,会通过线程刷新到磁盘,Flush List记录的是需要刷脏的页信息,指向LRU List的数据页。
    Free List、LRU List 、Flush List的联系。当访问数据的page在buffer pool中不存在时,需要Free List 申请内存来存储page信息,如果Free List没有空闲节点,则从LRU List尾部开始查找干净页淘汰,如果没有干净页,则从Flush List尾部开始进行单页的刷脏,释放节点到Free List中。
  • buffer pool的刷脏机制
    • redo log快满了
    • page cleaner线程刷脏
    • 脏页太多达到 innodb_max_dirty_pages_pct
    • 正常关闭mysql

索引

B+树

B+树相对是B树的进一步优化。B树的结构是每个节点存储key是data数据,通过度设置每一层存储的数据量。每个叶子结点高度都相同,节点两端有指针指向下一节点,叶子结点没有指针。所有的节点顺序都是按key排序的。这样的设计可以更具查询的数据范围快速查找需要的数据。B+树的不同在于,数据值存在叶子节点上,非叶子结点只存储数据的位置,这样每个节点存储的索引数据小,能存储的key更多,树的高度降低。b+树数据只存储在叶子结点,那么没次查询都需要到叶子结点获取数据,树的高度一致,所以没次查询的深度都一致,使查询更稳定。并且叶子结点数据是有序链表,便于范围查找,例如要查找18~20的数据,则只需要查找到18的数据然后链表往下查找符合的数据。innodb中只有主键索引的叶子结点存储数据,其他辅助的索引页字节点存储的是主键。那么如果查询的字段不在索引中,就需要再查一次主键索引拿到数据。树中每个节点的大小为一个页大小,因为innodb中数据都是按页为单位的,那么加载节点数据时只需要一次io读取。

索引优化

对于组合索引,遵循做匹配原则,例如对多个字段abc建立索引,那么有效的使用到索引的查询条件为a,a b,a b c。但是有的情况mysql优化器也可能会使用到索引。例如 where a=‘x’ and b like ‘%xx%’;这种情况,innodb会从索引中符合a=‘x’的索引中过滤出符合 b like ‘%xx%’的索引,而不是查询a=‘x’的所有索引,再将数据过滤符合b like ‘%xx%’的数据,这就是索引下推。这种语句用explain命令,可以看到extra信息是Using index condition;当时如果是where b=‘’ 这种是使用不到索引的。因为建立的组合索引是abc,那么在索引中查找的是a字段开头的数据,如果不符合首字段的查询,不能定位到索引。
对于范围查询,对于组合索引第一个字段,索引树能够定位到索引的数据范围,因为组合索引的第一个字段在索引树中也是有序的。但是有的情况优化器有会对范围索引进行优化导致不走索引,当扫描的数据量占总数据量的30%(这个值不同版本会有不同)时不走索引。

  • 关键字 FORCE INDEX ,能使sql强制使用某个索引
  • 查询频繁的字段上建索引
  • 区分度不高的字段不需要建索引
  • 索引数量不能过多,否则影响插入修改性能
  • 频繁修改的字段不适合建索引,因为会导致索引树的变化
  • 字段类型隐式转换,本来是varchar的字段,查询时没有加‘’,不能走索引
  • 使用函数方法计算字段的值,不能走索引。例如:max(id)=1
  • order by 也会影响索引的使用,在符合最左匹配原则,且排序规则都一致的情况下能使用索引

count() 和 count(1)和count(列名)区别 ps:这道题说法有点多
执行效果上:
count(
)包括了所有的列,相当于行数,在统计结果的时候,不会忽略列值为NULL
count(1)包括了所有列,用1代表代码行,在统计结果的时候,不会忽略列值为NULL
count(列名)只包括列名那一列,在统计结果的时候,会忽略列值为空(这里的空不是只空字符串或者0,而是表示null)的计数,即某个字段值为NULL时,不统计。

执行效率上:
列名为主键,count(列名)会比count(1)快
列名不为主键,count(1)会比count(列名)快
如果表多个列并且没有主键,则 count(1) 的执行效率优于 count()
如果有主键,则 select count(主键)的执行效率是最优的
如果表只有一个字段,则 select count(
) 最优。

事务

并发环境下数据库操作带来的事务问题

  • 更新丢失,两个事务同时读取同一个数据,事务A提交时,将事务B已经提交的数据更新为事务A的数据,导致数据丢失。
  • 脏读,事务A读取了事务B还没提交的数据,然后事务B回滚了,导致事务A获取的数据不正确。
  • 不可重复读,事务A中多次读取过程中,事务B更新或删除了数据提交了事务,导致事务A多次读取的数据不一致
  • 幻读,事务A多次读取的过程中,事务B新增了数据,导致事务A中多次读取的数据数量多了
    mysql的提供了四种事务隔离级别
  • 读未提交,允许读取其他事务还未提交的数据更新,没有解决脏读、不可重复读、幻读问题
  • 读已提交,允许读取其他事务已提交的数据,解决了脏读,未解决不可重复读、幻读问题
  • 可重复读,保证事务间多次读取数据是一致的,除非是事务自身修改了数据,解决了脏读、不可重复读,没有解决幻读问题
  • 串行,多有事务依次执行,解决了脏读、不可重复读、幻读问题。但是性能低。
    其中innodb中的可重复读使用间隙锁、临键锁都能解决幻读问题,所有innodb默认事务隔离级别已经能实现事务完整隔离。

MVCC

RR事务隔离通过mvcc,当事务第一次查询时,mvcc产生数据的快照,之后的查询都是差的快照数据,所以数据是不会变的。mcvv记录事务开启时数据的事务版本号,这个版本号标识有两个,一个是更新的版本号,一个是删除的版本号。当查询是判断版本号在本次事务之前的,删除的数据版本号未定义的。插入时将数据版本号更新为本次事务版本号。更新时更新数据行版本号为本次事务版本号,同时将原数据行删除版本号更新为当前事务版本号。删除时保存当前版本到到删除版本号标识。通过版本号的标识保证同一事务中多次查询的数据一致,解决不可重复读问题。
在进行事务提交前,innodb都会记录undo log,保留当前数据的状态,当发生回滚时,逻辑执行日志的记录,将数据回滚到事务操作前的状态。
sql中使用for update给操作加锁时,锁的对象时是索引,当查询的能定位到主键索引时锁定的是行数据。不能确定主键时的查询会导致锁表。对于范围查询innodb会加上间隙锁,将一定范围的索引加锁。对于非唯一索引键上的查询还设计临键锁是间隙锁+记录锁的实现。间隙锁和临键锁能解决幻读问题。所以innodb的RR事务隔离级别能解决事务带来的所有问题。

性能优化

  • 服务端
    mysql的优化不仅仅是sql的优化,服务端中链接数也会影响客户端链接,可以通过查看服务器状态检测当前链接情况。
  • 客户端
    首先sql优化可以通过explain查看sql执行计划,是否使用索引,索引使用正确与否。尽量使explain计划执行的type往性能高类型优化。建立合适的索引能加快sql查询、sql语句查询只返回需要的字段可能减少回表。
    应用使用的链接池技术框架,不同的框架性能有一定的差异,不够影响也不大。还有连接池大小配置,合适的大小能减少cpu时间片切换带来的性能影响,这个也与应用服务器有关。
    另外是分表,垂直拆分将不常用或者大的字段拆分到子表,水平拆分将数据按一定规则例如id取模、按时间分拆到相同表结构的不同的表上。
    再者就是业务上去适合表查询,例如历史记录可以按月去查,而不是一次过查询所有的历史记录。只能查询三个月的数据等等限制。

你可能感兴趣的:(MySQL基本知识总结)