Category: db

  • mysql调优带来的启示

    调优要抓住关键点.缓存也有缓存的弊端.如何让缓存价值更高.硬件层面解决问题.

    进行 mysql 管理优化,三大原则

    1. 访问内存数据的速度比访问磁盘数据速度快
    2. 将数据保存在内存里,尽可能减少磁盘活动
    3. 保留索引的信息比保留行的内容重要

    调优的基本方式

    1. 一次只改一个参数.如果同时修改多个变量,则很难对每个更改所产生的影响进行评估
    2. 逐步增大系统变量值.
      1. 避免一下子耗光系统资源
      2. 避免系统因为你将变量设置过高而变得异常快或者异常慢
    3. 不要在生成环境做测试
    4. 禁用不需要的存储引擎.减少内存使用.
    5. 保持简单的访问权限.在 MySQL 数据库中,权限表除了 user 表外,还有 db 表、tables_priv 表(对单个表进行权限设置)、columns_priv 表(对单个数据列进行权限设置)和 procs_priv 表(对存储过程和存储函数进行权限设置).当配置它们时,服务器在检查 sql 语句权限时,一定会检查它们的内容.

    一般调整的通用型系统变量

    1. back_log

      在处理当前连接时,排队等待连接的最大请求数.

    2. max_connections

      服务器支持的最大客户端并发连接数.可以使用show status查看变量Max_used_connections

    3. table_open_cache

      当服务器打开文件时,会试图将它们保持在打开状态,以减少必须要完成的文件打开操作和文件关闭操作的数量.可以通过show global status like 'Opened_tables'进行评估

      1. 如果Opened_tables迅速增大,则意味缓存太小
      2. 大一点的缓存可以减少缓存失效
    4. table_definition_cache

      与table_open_cache正相关,用于控制存储表定义的缓存大小.

    5. open_files_limit

      1. 考虑操作系统对进程所用使用的文件句柄限制
      2. mysql 本身也有自己的open_files_limit限制
    6. max_allowed_packet

      客户端通信的缓存区的最大值.默认 1MB ,允许的最大值为 1GB.可能还需要相应的增大 下面两个变量

    • read_buffer_size 读取操作使用的缓冲区大小
    • sort_buffer_size 排序操作使用的缓冲区大小

      每一个 session 都会受其影响,应该要逐步调整

    innodb 缓冲池

    基本组成

    1. 是个列表,并且分新旧两个子列表
    2. 改进的 LRU 策略,插入数据插入到旧的子列表前
      1. 如果数据块是查询需要的,会被访问后移到新子列表前面
      2. 如果是由于预读需要,可能不会被访问,减少了预读对缓存的影响

    一般调节的参数

    • innodb_buffer_pool_size

      缓冲池大小.单位为字节

    • innodb_buffer_pool_instance

      如果innodb_buffer_pool_size>=1GB && innodb_buffer_pool_instance>1,Innodb 会把缓冲池处理成多个小的缓冲池实例.通过随机分配的方式,减少并发竞争.

    • 影响缓冲池缓存失效的参数

      • innodb_old_blocks_pct 缓冲池的旧子列表所占的百分比.默认是 37.
      • innodb_old_blocks_time 一个缓存块在第一次访问之后,下次访问之前,需要在旧子列表待多少毫秒才移动到新子列表.(将其设置为大于 0,可以防止类似表扫描这种一次性访问大量数据的行为对缓冲池产生太大影响)

    考虑开启 mysql 查询缓存

    • select 语句执行后,服务器会记住这条查询语句的文本和它返回的结果
    • 查询缓存通过比较查询语句的文本进行比对,是否命中缓存
    • 查询返回的结果不确定则不会存储.例如使用时间 NOW()函数.
    • 表被修改,指向它的所有缓存查询都失效

    弊端

    • 为查询缓存分配过大的内存,会导致比较当前查询语句是否命中缓存花费过多时间.
    • 多客户端连接情况下,带来了并发的缓存竞争.(因为查询缓存只能单线程操作)

    硬件

    • 更多内存
    • 更强,更多处理器
    • 使用更快的硬盘
      • 全用 ssd
      • 利用机械盘组成 raid
      • 将磁盘活动分布到不同的物理设备上,充分利用并行特性
  • 为什么要索引

    为什么要索引

    没有索引查找数据要全表扫描

    索引如何起作用

    • 一种使用索引提高效率的方法:我们可以得知匹配行在什么地方结束,从而跳过其它部分
    • 另一种使用索引提高效率的方法:利用定位算法,不用从索引开始位置进行线性扫描,即可直接找到第一个匹配项(例如二分查找比扫描要快很多)。

    为什么不直接对数据排序

    如果一个表只有一个索引这样做事可以的,但是可能我们还想添加第二索引。例如用户表的id和名称字段。

    此外索引里的行通常比表里的数据行更短。当发生插入或者删除时,为保存排序顺序,来回移动较短的索引值,要比来回移动较长的数据行更加容易。

    索引如何影响mysql查询

    假设,你有3个表t1,t2,t3,其中每个表都包含一个列,它们分别为i1,i2,i3,并且每个表都有1000行记录。

    select t1.i1, t2.i2, t3.i3
    from t1 inner join t2 inner join t3
    where t1.i1 = t2.i2 and t2.i2 = t3.i3
    
    • 如果没有索引,需要尝试所有组合1000 * 1000 *1000

    • 如果i1,i2,i3都建有索引

      • 从表t1中选择第一个行,看该行包含的是什么值
      • 利用表2的索引,直接转到表t1的值相匹配的那个行。类似利用表t3的索引直接转到与表t1的值相匹配的那个行
      • 继续选择表t1的下一行,然后重复前面的过程。

      整个过程,只对表t1执行了全扫描操作。对表t2,t3执行的是索引查找。

    因此我们要

    1. 为用于搜索、排序或者分组的列(候选列)创建索引,而对于用作输出显示的列则不用创建索引。
      1. where
      2. order by and group by
    2. 认真考虑数据列基数(列能容纳的所有非重复值得个数)。

      基数越高,重复率越低,索引的使用效果越好。并且查询优化程序确定出某个值在表的行里出现频率很大时,它会跳过索引,转而执行全表扫描操作(因此性别列建索引没啥用)

    3. 索引短小值

      1. 数据类型越小,内存占用越小
      2. 索引的比较操作更快
      3. 键缓存的索引块可能容纳更多的键值
      4. 对于innodb 使用聚簇索引,数据行和主键值存储在一起。在二级索引里进行查找,会先得到主键值,再通过主键定位到相应的行。因此主键值会在一个二级索引里都会重复出现
    4. 索引字符串的前缀。

    5. 利用最左前缀。

      当创建包含n个列的复合索引时,实际上会创建n个专供mysql使用的索引。如果对一个表添加一个(country/state/city)的复合索引,那么索引会根据country/state/city的顺序来进行索引排序。因此country也是顺序的,可以直接使用country进行检索。

    6. 不要建立过多的索引

      1. 索引占用空间
      2. 表的修改需要对索引进行维护
    7. 让参与比较的索引保存匹配。
      1. 哈希散列,适合精确查找
      2. B树索引适合范围查询
    8. 利用慢查询日志找出那些性能低劣的查询

  • Innodb 逻辑存储结构

    Innodb 逻辑存储结构

    [TOC]

    来源

    《mysql技术内幕》

    从一条数据说起——InnoDB行存储数据结构

    MYSQL存储引擎INNODB详解,从底层看清INNODB数据结构

    22.2.1.1 Fil Header

    重要的

    • innodb B+树索引本身并不能找到具体的一条记录,能找到只是该记录所在的页。数据库把页载入到内存,然后通过page directory在进行二叉查找具体的记录。
    • 数据记录在页中是以堆的形式存放的
    • VARCHAR 或者 BLOB 对象大的时候,使用溢出页面存放实际的数据。

    (more…)

  • innodb存储引擎

    innodb存储引擎关键点

    [TOC]

    缓存池

    • 索引页

    • 数据页

    • undo页

    • 插入缓存

    • 自适应哈希索引

    • innodb引擎的锁信息

    • 数据字典信息

    innodb 内存数据对象

    mysql> use information_schema;                                                   Database changed
    mysql> select pool_id,pool_size, free_buffers,database_pages from innodb_buffer_pool_stats \G;
    *************************** 1. row ***************************
           pool_id: 0
         pool_size: 8192
      free_buffers: 1024
    database_pages: 7160
    1 row in set (0.00 sec)
    

    缓存池管理

    innodb_buffer_pool_list

    • LRU(默认)
      • 频繁使用的在LRU列表前端
      • 优化:新访问的页不是放在列表前端,而是放在Midpoint。避免扫描全表的sql,将热点数据挤压刷出
      • midpoint之前称为新表(new sublist),表示热点数据
      • midpoint之前称为旧表(old sublist)
    • unzip_LRU
      • 管理非16KB的页
      • LRU列表中包含unzip_LRU列表中的页

      在LRU列表中的页被修改后,成改页为脏页(dirty page)。

      数据块通过checkpoint机制将脏页刷新回磁盘

      Flush列表中的页为脏页列表。脏页同时存在于LRU列表和Flush列表

    重做日志缓存(redo log buffer)

    • 先将redo信息放入redo log buffer
    • 然后按照一定频率刷新到redo log file(一般1秒一刷)
    • flush redo log
      • master thread 一秒刷redo log buffer to redo log
      • 每个事务提交时
      • 当redo log buffer 剩余空间少于1/2时

    额外的内存池

    • 一些数据结构本身的内存优先从(额外的内存池)中分配
    • 当(额外的内存池)内存不足,会从缓存池中分配

    checkpoint技术

    面对的难题

    • 缓存池不可能缓存数据库所有数据
    • redo log不可能无限大

    用于解决

    • 缩短数据库恢复时间
    • 缓冲池不够用时,将脏页刷新到磁盘
    • 重做日志不可用时,刷新脏页

    通过LSN(log sequence Number)来标记版本的。

    • LSN是8字节的数字
    • 每个页有LSN
    • redo log有LSN
    • checkpoint 有LSN

    有两种checkpoint

    • Sharp checkpoint,数据库关闭时,将所有脏页刷新回磁盘
    • Fuzzy checkpoint,数据库运行中使用,进行页的刷新,只刷新一部分脏页回到磁盘
      • master Fuzzy checkpoint。异步定时从缓冲池刷脏页到磁盘
      • FLUSH_LRU_LIST Checkpoint 为了保证LRU LIST有差不多100个空闲页可供使用,Page Cleaner线程中执行
      • Async/Sync flush Checkpoint.
      • dirty page too much Checkpoint

    MASTER THREAD

    • 具有最高的线程优先级
    • 由多个loop循环组成
      • 主循环(loop):每秒操作和每十秒操作
      • 后台循环(backgroup loop)
      • flush loop
      • 暂停循环(suspend loop)

    loop

    每秒操作

    • 刷redo log buffer到磁盘,即使这个事务还没有提交(总是)

      解析了为什么再大的事务commit都很快

    • 合并插入缓冲(可能)

      只有io压力很低,前一秒的io总数<5,才会执行。

    • 至多刷100个Innodb 缓冲池中的脏页到磁盘(可能)

      当前缓存池中的脏页比例buffer_get_modified_ratio_pct大于配置值

    • 当前没有用户,切换到backupgroup loop状态(可能)

    每十秒操作

    • 刷100个Innodb 缓冲池中的脏页到磁盘(可能)
    • 合并至多5个插入缓冲(总是)
    • 刷redo log buffer到磁盘(总是)
    • 删除无用的undo页(总是)
    • 刷100或者10个Innodb 缓冲池中的脏页到磁盘(总是)

    backupgroup loop

    • 删除无用的undo页(总是)
    • 合并20个插入缓冲(总是)
    • 跳回主循环(总是)
    • 不断刷100个Innodb 缓冲池中的脏页到磁盘(总是)(可能跳转到flush loop中完成)

    InnoDB1.0.x之后

    1.0.x版本之前,Master Thread 有了一定的hard coding。随着硬件性能日新月异,这对编码方式不能很好的匹配不同性能的硬件。

    • 使用配置代替hard coding
    • 增加了一些参数自适应

    InnoDB1.2.x之后

    刷新脏页的操作从Master Thread分离到一个独立的Page Cleaner Thread。减轻Master Thread 的工作,同时进一步提高了系统的并发性。

    Innode关键特征

    • insert buffer
    • double write
    • adaptive hash index
    • async IO
    • Flush Neighbor Page

    insert buffer

    insert buffer 和数据页一样,也是物理页的一个组成部分,用于非唯一辅助索引的插入操作。

    每一张表上除了有一个聚集索引(primary key),有可能还有多个非聚集的辅助索引。

    在进行插入操作的时候,数据页还是按照primary key进行顺序存放的。但是对于非聚集索引叶子节点的插入不再是顺序的。这时需要离散的访问非聚集索引页,由于随机读取的存在,进而影响了插入性能。

    mysql引入insert buffer,对非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若是在,则直接插入;若不在,则先放入到一个insert buffer对象中。

    然后再以一定的频率和情况进行insert buffer和辅助叶子节点的merge操作。

    辅助索引不能是唯一的,因为在插入缓冲时,数据库为了避免离散读取的情况发生,并不去查找索引页来判断插入的记录的唯一性。

    Change Buffer

    Innodb 1.0.x版本开始引入Change Buffer,可将其视为Insert BUFFER的升级。Innodb可以对DML操作–INSERT、DELETE、UPDATE都进行缓冲,它们分别是:

    • Insert Buffer
    • Delete Buffer
    • Purge Buffer

    Innodb 提供参数Innodb_change_bufferiing,用于开启各种Buffer的选项

    对一条记录进行UPDATE操作可能分为连个过程:

    • Delete buffer复制将记录标记为删除

    • Purge buffer负责将记录真正的删除

    Insert Buffer的内部实现

    • 使用b+树

      mysql 4.1之前每张表有一颗insert buffer B+树。现在全局只有一颗Insert Buffer B+树

    • 它保存在共享空间中(ibdata1),因此,试图通过独立表空间ibd文件恢复表中数据时,往往会导致CREATE TABLE失败。这是因为表的辅助索引中的数据可能还在Insert Buffer中。所以通过idb文件进行恢复后,还需要进行REPAIR TABLE操作来重建表上所有的辅助索引。

    • 由叶子节点和非叶子节点组成

    • 非叶子节点存放的是查询的search key(键值),结构如下

    space marker offset
    表示待插入记录所以在表的表空间id 用于兼容向后兼容 页所在的偏移量
    • 当一个辅助索引要插入到页(space,offset)时,如果这个页不再缓冲池中,那么Innodb首先根据上述规则构造一个search key,接下来查询insert buffer,然后将记录插入到insert buffer B+树的叶子节点中。
    space :marker offset metadata
    • metadata中IBUF_REC_OFFSET_COUNT(2字节的整数)用来排序每个记录进入insert buffer的顺序

    • insert buffer bitmap:一个特殊的页,用来标记每个辅助索引页的可用空间,保证每次merge insert buffer页必须成功。

    • 每个insert buffer bitmap用来追踪16384个辅助索引页,也就是256个区(Extent)

    • 每个辅助索引页在insert buffer bitmap中存储结构

    merge insert buffer

    • 辅助索引页被读取到缓冲池中
      • 检查insert buffer bitmap页,确认该辅助索引页是否存放在insert buffer B+树中
      • 存在,则将B+树中该页的记录插入到该辅助索引页中。对该页多次的记录操作通过一次合并到原有的辅助索引页
    • insert buffer bitmap页追踪到该辅助索引页已无可用空间时。(小于1/32页,会强制进行一次合并操作)
    • Master Thread中每秒或者每十秒的合并操作。

    两次写

    避免数据库宕机时,出现部分写失效(partial page write)。例如某个页16KB,只写了4KB就发生了宕机。

    通过redo log进行数据恢复,由于redo日志记录的是对页的物理操作(如偏移量800,写‘aaa’记录),需要基于页是好的。如果这个页本身已经发生了损坏,无法通过redo 日志进行重放。

    doublewrrite:当写入失效发生时,先通过页的副本还原该页,再进行重做。

    doublewrrite由两部分组成:

    • 内存中的doublewrrite buffer,2MB
    • 物理磁盘上共享表空间中连续的128个页,即2个区(extent),2MB

    在对缓冲池进行flush时,并不是通过写磁盘,而是通过memcpy函数将脏页先复制到内存中的doublewrite buffer,之后通过doublewrite buffer再分两次,每次写1MB顺序写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘避免缓冲写带来的问题(顺序写)。doublewrite buffer中的页写入到各个表空间文件中。(离散写)

    自适应哈希索引(AHI)

    自适应哈希索引:Innodb存储引擎会监控对表上各索引的查询,如果观察到建立哈希索引可以带来速度提升,则建立哈希索引。

    AHI是通过缓冲池的B+树页构造,不需要对整张表构建哈希索引。

    条件:

    • 访问模式一样,即查询的条件一样。
    • 以该模式访问了100次
    • 页通过该模式访问了N次,其中(N=页中记录*1/16)。

    异步IO

    Innodb支持AIO。

    • read ahead 方式的读取(借助aio,将所有io发送完毕后,然后等待所有IO操作的完成)
    • 脏页的刷新(可以将多个连续io进行IO Merge)

    刷新邻近页

    当刷新一个脏页时,Innodb会检测改页所在区(extent)的所有页,如果是脏页,那么一起进行刷新。然后通过AIO将多个io写入操作合并成一个IO操作。

    缺点:

    • 可能会将不怎么脏的页进行了写入,而该页之后很快又会变成脏页。
    • 固态硬盘有着较高的IOPS,不需要这个特性。
  • 如何进行k/v存储选型

    下面是bitcask的论文里,作者考虑到的目标

    • low latency per item read or written
    • high throughput, especially when writing an incoming stream of random items
    • ability to handle datasets much larger than RAM w/o degradation
    • crash friendliness, both in terms of fast recovery and not losing data
    • ease of backup and restore
    • a relatively simple, understandable (and thus supportable) code structure and data format
    • predictable behavior under heavy access load or large volume
    • a license that allowed for easy default use in Riak

    论文地址:https://riak.com/assets/bitcask-intro.pdf