date: 2017-01-12 13:19
来源: inside mysql 微信公众号 - 最喜欢的mysql书籍评选
http://www.highperfmysql.com, 在线源码的网站, 然并卵..
架构和历史
逻辑架构
MySQL整体逻辑架构
MySQL整体逻辑架构
第一层: S/C 服务架构, 包括 连接处理, 授权认证, 安全
第二层(大部分核心功能): 查询解析 分析 优化 缓存 内置函数(日期 时间 数学 加密) 跨存储引擎功能(存储过程 触发器 视图)
第三层: 存储引擎 -> 数据 存储/提取 + 服务器通过 api 进行通信
连接管理: 每个客户端连接 -> 服务器线程(缓存)
安全: 连接验证 -> client_ip + username + password + ssl证书; 特定查询的权限
解析查询(优化+执行): 重写查询 + 表的读取顺序 + 索引选择; explain; select -> 优先 query cache
并发控制
读写锁: 共享锁/读锁(shared lock/read lock) + 排它锁(exclusive lock/write lock)
锁每时每刻都在发生 -> A 修改数据u-> 锁定, 不让 B 读取这部分数据
锁粒度: 理想状态 -> 只对修改的文件进行精确的锁定; 锁定数据越少 -> 并发量越大; 加锁需要消耗资源(获得/检查/释放)
表锁(table lock): alter table; mysiam
行锁(row lock): innodb
事务: 一个独立工作单元; 要么全部执行成功, 要么全部执行失败
银行应用: 事务概念 + 如果执行都某一步服务器崩溃了
ACID: atomicity 原子性; consistency 一致性; isolation 隔离性; durability 持久性
隔离级别
死锁: 多个事务试图以不同顺序锁定资源; 死锁检测 + 死锁超时机制
事务日志: 修改行为 -> 事务日志(磁盘) -> 数据持久到磁盘, 其实就是多次操作合并到一次; 预写式日志(write ahead logging)
mysql中的事务: 默认采用 autocommit 模式, 每个sql都当做一个事务; two-phase locking protocol -> 隐式锁(根据隔离级别) + 显式锁 -> rollback/commit 时一起释放
多版本控制(MVCC): 通过保存每个时间点的快照实现; 类似 row-lock, 但是很多情况下避免加锁操作, 开销更低; innodb 实现 -> 每行包含2个隐藏列, 创建时间/删除时间 -> 实际保存 系统版本号(system version number)
存储引擎
innodb: mvcc->高并发; 默认 repeatable read + next-key locking(间隙锁, 防幻读); 数据存储 -> tablespace(表空间); 聚簇索引; 预读(adaptive hash index) + insert buffer + 热备; 单机承载 3~5 TB
OLTP类存储引擎 -> Tokudb 引擎 -> 使用 分形树(fractal trees) 的索引数据结构 -> 日志型应用
面向列的存储引擎 -> infobright(数十TB) -> 数据分析/数据仓库
引擎选择: innodb 适用于大部分场景; 事务 + 备份 + 崩溃恢复 + 特性
只读测试
# 显式开启事务
start transaction;
sql
commit;
# 变量
show variables like 'autocommit' # 查看 autocommit 值
set autocommit 1 # 开启 autocommit, 一般 1->ON 0->OFF
set session transaction isolation level read committed # 设置隔离级别
show table status like 'user' # 也可以查询 information_schema
select count(*) from table # mysiam 表有记录, innodb 存储的是估计值, 需要查询才能知道
# 转换表的引擎
alter table mytable engine=innodb # 慢
mysqldump # 导入导出, 注意导入会自动在 create table 前加入 drop table
create table new_t like old_t # create + select
alter table net_t engine=innodb
insert into new_t select * from old_t where id between x and y # 数据量大要考虑分片, 并在这里加上事务
基准测试
why: 系统在不同压力下的表现 -> 评估系统容量; 验证假设 + 重现异常 + 当前运行情况 + 更高负载 + 规划业务增长 + 适应 + 不同环境(硬件/os) + 验证配置
策略: 集成式(full-stack + 单组件式(single-component)
指标: 吞吐量 -> 单位时间内事务处理数; 响应时间/延迟, 通常使用 百分比响应时间; 并发性(并不是所有的web并发都会产生mysql并发); 可扩展性
设计和规划: tpc-h -> 电子商务系统; 单元测试(多次); web log + mysql query log; 规划 + 文档; 一般都需要运行多次 -> 自动化
获取系统性能和状态: cpu, 磁盘i/o, 网络i/o, show global status
, pt-diskstats
工具: ab http_load jmeter mysqlslap sql-bench percona:tpcc-mysql
# benchmark()
set @input = 'hello world'
select benchmark(100000, md5(@input))
select benchmark(100000, md5(@input))
# http_load
http_load -parallel 10 -rate 5 -seconds 10 usrls.txt # 并发10 每秒请求5次 10s内
# sysbench
服务器性能剖析 - profile
如何确认服务器达到最佳状态; 某条语句执行不够快; 诊断用户故障(停顿/堆积/卡死)
性能即响应时间
任务时间 = 执行时间(分析子任务, 是否可以合并等处理) + 等待时间(包括其他系统间接影响, cpu, 磁盘io)
目前最好的2中方式:
慢查询日志: pt-query-digest
-> 生成慢查询日志剖析报表 -> 方差均值化(variance-to-mean ratio) / 离差指数(index of dispersion) -> 执行时间变化较大
pt-query-digest
+ tcpdump
show profile
-> performance schema -> sys schema
间歇性问题: 单查询 vs 全服出现的问题
捕获诊断数据:
诊断触发器: 误报(false positive) + 漏检(false negative); 阀值
数据收集工具: pt-collect
解析结果数据: pt-stalk
; 快速解析 -> pt-sift
gdb -> pt-pmp
资源效率低: 过度使用, 余量不足; 没有被正确配置; 损坏/失灵
show VARIABLES like 'long_query_time' # 慢查询阀值设置
show processlist / show full processlist # 查看线程状态, 如果是 root 可以查看到全部, 非root只能查看到当前用户发起的线程
# profile
show profile
show profiles # 包含每个执行的 query, 里面有 query_id
show profile for query 1
set profiling=1
# 计数器
show status
show global status
schema 与数据类型优化
数据类型
更小通常更好 -> mysql 通常使用固定内存块来保存内部值 -> 内存临时表排序/操作 + 磁盘临时表排序
简单(数据类型)就好
尽量避免null: 导致索引复杂, 默认都是 null
year date time
timestamp: from_unixtime() / unix_timestamp()
; 行为规则比较复杂, 在不同版本的还可能变动, 使用/修改后要使用 show create table
确认
timestamp vs datetime: 时间范围小, 一般的存储空间, 跟随时区变化, 特殊的自动更新能力(特殊能力可能会成为障碍)
整型: tinyint smallint mediumint(24位) int bigint + unsigned; 整数计算 -> 当做 bigint 来处理; int(11) 不限制值的范围, 只是方便客户端显示
实数: float/double -> 浮点近似计算(系统原生实现); decimal -> 浮点精确计算(mysql实现) / 财务数据
mysql 使用 double 作为内部浮点计算类型; 可以考虑 float -> int 来避免浮点数运算
varchar: 变长, 1-2个字节存储长度信息; 场景 -> utf8 / 最大长度远大于平均长度 / 更新少
char: 定长; 存储时删除末尾空格; 场景 -> 长度趋近一个近似值, 如 md5
text: tinytext smalltext mediumtext longtext; innodb 使用外部存储区存储, 保留 1-4 字节的指针; max_sort_length
binary vs varbinary vs blob
尽量减少使用 text 和 blob; substring() -> 转化为使用 内存临时表, 注意 max_heap_table_size
+ tmp_table_size
explain -> extra -> using temporary: 使用了 隐式临时表
enum: 替代字符串类型; 数字<->字符串 映射, 保存在 .frm
文件中; 按映射的整数进行排序; 需要 alter table
来添加/删除字符串; 查找转换开销
位数据类型: 技术上来说都是字符串类型
bit: 最大 64 位; 二进制 -> 整数 -> 字符串场景(ascii) + 数字场景(直接使用)
单个 true/false: bit(1); char(0), 使用 null/空字符串 表示 false
set: 保存多个 true/false 值, find_in_set() / field()
; 大表上 alter table
开销大, 也难以建索引
混用不同数据类型 -> 隐式类型转换
标识列(identifier column): 尽量使用 自增id
uuid值: unhex()
+ binary(16) 存储; hex()
检索
当心自动生成 schema
ip地址: varchar(15) -> uint + inet_aton() inet_ntoa()
陷阱
太多的列
太多关联: 实体-属性-值(EAV) 设计模式 -> 常见的糟糕设计模式
全能/变相枚举
not invent null: 因为不推荐使用null值而引入替代的值, 如 0/空字符串, 但是引入的值不一定能替代, 比如 全0的datetime
范式 vs 反范式
范式: 更少重复数据 -> 表通常更小, 更容易放到内存 + update 通常更快 + 更少使用 distinct/group by; 需要 join
反范式: 减少 join, 避免随机io
缓存表 vs 汇总表
重建 -> 影子表
汇总表: 实时计算统计值代价通常很昂贵, 通过设置时间周期的统计, 然后基础统计之后的数据再做统计, 效果要好很多
缓存表: 需要使用不同的索引组合来加速各类查询, 例如 sphinx
物化视图: 根据修改日志来增量改变数据, 而不是重新查询
计数器: 应对并发 -> 预先加入多列, 随机更新其中一列
更快的读, 更慢的写
# 影子表
drop table if exists t_new, t_old
create table t_new like t
rename table t to t_old, t_new to t
alter table x alter column y set default 5 # 修改列的默认值
# 修改 .frm: auto_increment + enum/set 添加/删除 常量
flush tables with read lock
# 替换 .frm 文件
unlock tables
高性能索引
基础
mysql 只能高效的使用索引的最左前缀列, 所以索引的顺序很重要
索引结构 大部分基于 b-tree, 适合 查找/范围查找/更新
适用范围: 全值匹配 / 最左前缀 / 列前缀 / 范围值 / 精确某一列 + 范围匹配另一列 / 只反问索引的查询 / order by
限制: 匹配结尾值; 不能跳过索引中的列; 某列使用范围查询, 则右边所有的列无法使用索引优化查找
hash索引: 索引结构非常紧凑, 速度非常快; 无法排序; 不支持部分索引列匹配; 只能等值比较查询; 哈希冲突
空间数据索引(r-tree): 地理数据存储; GIS 支持
全文索引: match against > where
优点: 减少需要扫面的数据量; 避免排序和临时表; 随机io -> 顺序io
优化查询但是会降低修改的性能; TB级别数据, 定位单条数据意义不大, 经常会使用块级别元数据
高性能
独立的列: 索引列不能是表达式的一部分
索引的选择性: 不重复记录值 / 数据总数, 数值越大, 性能越好
索引前缀: 足够长的前缀保证较高选择性, 同时不能太长(节约空间); 计算方式 -> 接近完整列的选择性; 无法 order by/group by, 无法覆盖扫描; session -> 8位; 后缀索引 -> 电子邮件
多个单列索引 vs 多列索引: or 无法使用到多个单列索引; and 使用多列索引更好; 使用 explain 来查看索引合并
索引顺序: 不考虑排序和分组时, 选择性更高的列放在前面
聚簇索引: 最好使用自增id; 高并发下主键上界会成为 '热点'
覆盖索引: 索引中包含所有需要查询的字段的值; 避免二次查询(\不要 select *
的理由之一); 延迟关联(deferred join) -> 先使用覆盖索引查询, 然后再查询所有列
索引排序: explain -> type -> index; 设计的索引既能满足查找, 又能满足排序
冗余索引: (a,b) + (a) -> 冗余; (a,b) + (b,a) -> 非冗余
重复索引: pri(id) + ind(id)
无用索引: pt-index-usage
案例
使用 (sex, country) 作为通用索引前缀; 使用 where sex in ('f','m')
使用使用到索引
age 放在最后, 尽可能把需要范围查询的字段放在索引后面
处理 order by + limit offset
维护
innodb设计上保证不容易损坏 -> 出了问题(一般是硬件), 就需要立刻查看
更新索引统计信息: records_in_range() info()
减少索引和数据碎片
总结
单行访问很慢的: 最好读取的块中包含尽可能多所需要的行
顺序访问很快的: 顺序io不需要多次磁盘寻道 / 不需要额外排序操作)
覆盖查询很快: 不需要再回表查询所有行
# url 实现触发器实现 hash 索引
create trigger t_ins before insert on t for each row begin set new.url_crc=crc32(new.url); end;
create trigger t_upd before update on t for each row begin set new.url_crc=crc32(new.url); end;
conv(right(md5('url'), 16), 16, 10) as hash64 # 自定义 hash64 实现
select * from t where url_crc=crc32(url) and url='xxx' # 生日悖论, 查询时使用带双条件
set optimizer_switch # 设置索引合并功能
ignore index # 让优化器忽略掉某些索引
# 延迟关联: limit offset
select cols from t join (select id form t where x.sex='m' order by rating limit 10000,10) as x using(id) # 索引: (sex, rating)
show index from t # 分析某张表上的索引
查询性能优化
不要轻易 select *
低效查询分析步骤: 检索大量超过需要的数据 -> row/column; mysql 服务层分析大量超过需要的数据行
确定查询只返回需要的数据: 查询了不需要的记录 -> 加 limit; 多表关联时返回全部列; 总是取出全部列; 重复查询相同数据 -> 缓存
衡量查询开销: 响应时间(服务时间 + 排队时间) 扫描的行数 返回的行数
扫描行数和访问类型: explain -> type
; 全表扫描 索引扫描 范围扫描 唯一索引查询 常数引用
mysql 3种方式应用 where 条件: 索引(存储引擎层); 索引覆盖扫描 explain -> extra -> using index
(服务层, 但不需要回表查); 从数据表中过滤记录 explain -> extra -> using where
(mysql 服务层)
优化扫描行数: 索引覆盖; 改变库表结构 -> 单独汇总表; 重写复杂查询
事务型: 很多时候 小事务 更高效
切分查询: 一次全部删除 -> 一次10w条
分解关联查询: 缓存中间结果; 减少锁竞争; 在应用层关联, 更容易 拆分/扩展; 可能的查询效率提升; 较少冗余记录查询; 在应用层实现 hash 关联
sql 语句 -> 解析: 解析树, 语法规则验证+解析查询 -> 预处理器: 进一步检查解析树, 检查 表/列/别名 -> 优化器 -> 执行计划
优化器: 子查询 等等
查询执行基础
查询执行路径
c/s通信协议: 半双工 -> 简单快速; 抛球游戏, 同一时刻只有一端可以控制, 只有全部接受信息才能响应, 无法进行流量控制
查询状态: sleep / query / locked / analyzing and statistic / copying to tmp table / sorting result / sending data
查询缓存: 使用大小写敏感 hash 实现, 即使只有一个字符不同也无法使用到查询缓存
# 分解 join
select * from tag t join tag_post tp on tp.tag_id=t.id join post p on tp.post_id=p.id where t.tag='mysql'
select id from tag where tag='mysql'
select post_id from tag_post where tag_id=xx
select * from post where id in (xxx)
show_allowed_packet # config: 限制查询语句长度
show full processlist # 查询状态
相关
B树、B-树、B+树、B*树: http://www.cnblogs.com/oldhorse/archive/2009/11/16/1604009.html
mysql优化之路----hash索引优化: http://www.jb51.net/article/54089.htm(url索引优化)
hash 冲突 - 生日悖论(百度百科): http://baike.baidu.com/link?url=olZRWVyk5__5nieivzCU64iDgFky71h_KugHNUCgX3mbrVhcs-guqlztw5uJFQqpeyEKw5SRbJPu6VL4tibHCq
APM: new relic
flexviews 物化视图
percona-toolkit工具包的使用教程: http://bbs.chinaunix.net/thread-3751657-1-1.html
linux系统工具:
vmstat
iostat
lsof # 查看系统打开的文件句柄
df -h # Show information about the file system
strace -cfp $(pidof mysqld) # 调查系统调用情况
oprofile # System wide profiler