Mysql 学习

1、索引

  • 背景:
    访问磁盘,机械硬盘依靠的是机械运动, 寻道时间(磁臂移动到指定磁道)5ms - 旋转延迟(转速 如7200转 表示1min能转7200,120转每秒,1转的延迟就是1/120/2 ~ 4.17ms) - 传输时间 相较前两个可以忽略不计。
    所以IO访问是代价高昂的,操作系统一般会做页缓存(把目标数据附近的数据也读入内存缓存)。一般IO读取的单位是页,每页的大小是4K或8K。
    索引的目的就是减少磁盘IO的次数,加快查找读取。
    依赖数据结构:
  • B+树 :
    1、IO次数取决于树的高度,h=log(m+1)N , 总数据数量N一定的情况下,每个磁盘块的数据数量m越大,高度越低。m=磁盘块的容量大小(4k,8K)/数据的单条大小,数据页的大小一定的,所以要让m越大,则单条数据的数据量要越小。这就是为啥索引字段要尽量小,B+树内层节点不存储数据,只有叶子结点存储数据。
    2、B+树的数据是复合数据结构时,他会先比较前面的(name,age,sex)name ,即最左匹配原则
  • Mysql索引建立原则
    1、最左前缀匹配原则 mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配 这个指的是你建立的索引顺序,不是where语句的顺序 != isnull isnotnull 无法使用索引
    2、= 和 in 索引中字段可以乱序
    3、尽量使用区分度高的列作为索引
    4、查询sql中,不要将索引包在函数或计算中,可能会使用不到索引
    5、尽量扩展索引,不要新建索引 a,b -> a,b,c
  • explain 结果的含义


    image.png
  1. type:表的连接类型
    system仅有一行的系统表 - 特殊
    const数据表只有一行匹配,查询时会当做常量处理 常用于primary key,unique key等
    eq_ref每次与之前的表合并都只有读取一行,使用primary key,unique key 进行join
    ref每次与之前的表合并都只有读取少量行,多用于各个key的=或<=> 覆盖索引或非主键或非唯一键
    range常数值范围
    index索引树扫描
    all全表扫描
    system>const>eq_ref>ref>range>index>all
  2. possible_keys 能用这些索引找到行
  3. key 查询实际使用的索引
  4. key_len 实际使用索引的长度(字节),根据这个可以推算联合索引用了多少列
  5. rows 实际扫描行数
  6. extra 表示查询时实际使用的详细信息
    using temporary 使用了临时表 很影响性能
    using index 使用了索引覆盖
    using where 使用了where,但是需要回表查数据
    using index condition 使用索引查找,但是需要回表查数据
    using index & using where 使用索引查找且不需要回溯数据
  • 分类
    聚集索引:按照每张表的主键构造一颗B+树,叶子结点中存放了表的行数据,故叶子结点又称数据页。除了索引能定位页的地址,相邻页之间也能通过双向链表查找,属于逻辑上连续。
    非聚簇索引:b+树的叶子结点上的数据是指向数据实际存储地址的指针,也就是还需要一步查找

InnoDB :主键是聚簇索引,辅助索引是非聚簇索引
MyISAM:都是非聚簇索引,数据项单独存储

2、主从

3、binlog redolog undolog

innoDB 在一条记录要更新时,会先写日志redolog,再更新到内存,到这算更新结束。持久化到磁盘的时间根据策略来。redolog和binlog都是顺序写,速度快

  • redo log 保证事务的持久性
    物理日志,记录物理页的修改,而不是记录修改了某行数据。用来恢复提交后的物理页
    用来保证意外crash后,恢复还未持久化的数据
    redo是顺序写文件,快;脏页是随机IO,慢
    redo只用写需要修改的部分内容,少;脏页刷新是整页写入,大
  • undo log 保证事务的一致性
    用来做回滚和mvcc
  • bin log :
    记录的是表结构的创建或更新,表记录的创建或更新 的二进制文件 记录的sql语句

redo log 是innoDb特有的,bin log是server层的,所有引擎都有
redo log 是记录的物理日志,bin log是sql日志
redo log 是循环记录到固定空间,如果write_pos赶上了checkpoint,空间会用完,bin log 是追加写(会分多个文件),理论可以无限写

4、非常规sql

#如果不存在记录,直接插入;如果存在,删除后再插入
replace into t(id, update_time) values(1, now());
#mysql 局部变量实现rank,无并列
select id,maxScore,(@rowNum:=@rowNum+1) as rowNo
from t_user,
(select (@rowNum :=0) ) b
order by t_user.maxScore desc 
#mysql 局部变量实现rank,实现并列名次问题
select fee_rate,
       @`rank`:=@`rank`+1 as n,
       @rank1:=(case
           when @fee = fee_rate then @rank1
           when @fee:=fee_rate then @`rank`
           end ) as r from fund_zbank.fee_rate_config a, (select @rank:=0, @rank1:=0, @fee:=null) b order by fee_rate desc;
# having是对分组后的结果进行where
# 筛选各个部门的部门人数大余2的最大和最小工资
SELECT dept ,MAX(salary) AS MAXIMUM ,MIN(salary) AS MINIMUM
FROM STAFF
GROUP BY dept
HAVING COUNT(*) > 2
ORDER BY dept
# 获取分组前3
select * from (
select fee_rate,
       if(@ol_merchant=merchant_id, @rr:=@rr+1, @rr:=1) as rr,
       @ol_merchant:=merchant_id
from fund_zbank.fee_rate_config a,
     (select @rr:=1, @ol_merchant:=null) b
order by merchant_id, fee_rate ) c
where c.rr <=3;
# 获取分组前3,含并列
select c.merchant_id, c.fee_rate, c.rp from (
                  select fee_rate,
                         merchant_id,
                         case
                             when @ol_merchant = merchant_id and @ol_fee = fee_rate then @rr
                             when @ol_merchant = merchant_id then @rr_1 + 1
                             else 1
                             end
                             as rp,
                         @rr_1 := if(@ol_merchant = merchant_id, @rr_1 + 1, 1),
                         @rr := if(@ol_merchant = merchant_id and @ol_fee = fee_rate, @rr, @rr_1),
                         @ol_merchant := merchant_id,
                         @ol_fee := fee_rate
                  from fund_zbank.fee_rate_config a,
                       (select @rr := 1, @ol_merchant := null, @rr_1 := 1, @ol_fee := null) b
                  order by merchant_id, fee_rate
              ) c
where c.rp <= 3;

5、锁

按粒度分:

  • 表锁 开销小,锁的快,不会出现死锁;粒度大,锁冲突概率高,并发度低 -- 查询为主,少量更新
  • 行锁 开销大,加锁慢,会出现死锁;粒度小,锁冲突概率低,并发度最高 -- 更新频繁,且需要查询
  • 页锁 介于表锁和行锁之间

按引擎:

  • MyISAM
    show status like 'table%' 查到的table_locks_waited值表示等待加表锁的数目,如果值很大,说明当前锁争用激烈

表级锁有:

  1. 读共享锁
  2. 写独占锁
    MyISAM 写锁比读锁优先级更高,如果更新频繁,可能导致读永远排队,无法查询
  • innoDB
    show status like 'innodb_row_lock%' 查找InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值,如果很大,说明当前锁争用激烈

行锁有:

  1. 共享锁S 允许事务读,不允许排他锁获取相同数据集
  2. 排他锁X 允许事务写,不允许其他共享读锁或排他写锁获取相同数据集

为了行锁表锁共存的锁粒度机制,使用表锁 意向锁:

  1. 意向共享锁IS
  2. 意向排他锁IX
  • 意向锁是innoDB自己加的,用户无需干预
  • 对于update,insert,delete,innoDB会自动给相关数据集加排他锁X
  • 普通select不会加锁

事务中,自己手工加锁

  • SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE 加共享锁,其他事务任然可以查询该数据集,而且也能加共享锁。如果当前事务要对记录更新,可能会死锁
  • SELECT * FROM table_name WHERE ... FOR UPDATE 加排他锁,其他session任然可以查询该记录,但是不能获得锁了,会发生加锁等待

事务中,隐式加锁
在事务中,会根据隔离级别,自动上锁,在commit或rollback后,自动释放

行锁是加在索引上的,所以只有命中索引才会用到行锁,否则使用表锁

间隙锁 gap lock
当使用范围查询时,会给范围内的索引且存在记录加锁,但是在范围内不存在的记录也会加锁,叫做间隙锁。这样会造成不允许间隙内的记录插入。 主要是为了防止幻读,满足恢复和复制的目的。

死锁:
多个事务同时请求锁定对方占用的资源
innoDB能检测死锁的依赖,并返回错误,回滚返回错误的那个事务。无法检测到的外部锁,可以通过设置获取锁的超时时间 innodb_lock_wait_timeout 。高并发场景下,多个线程等待同一个锁时,死锁检测会导致速度变慢。

避免死锁:

  • 并发写入事务开始时,select ... for update 先获取要修改的行的锁,再执行修改
  • 事务中,如果先查后改,需要先获取排他锁,直接先获取排它锁
  • 多个表访问时,尽量互相保证获取各个表中锁的顺序性

6、事务

  • A automicity 原子性 一个事务要么成功,要么全部回滚 使用undolog实现回滚
  • C consistency 一致性 事务的目的就是一致性,AID都是为了保证一致性
  • I isolation 隔离性 事务内部的操作和其他事务是隔离的,各个并发事务互不干扰

写操作对写操作的影响:使用锁机制保证
写操作对读操作的影响:使用mvcc机制保证
脏读:A事务读取到B事务未完成提交的数据
不可重复读:A事务前后两次读取到B事务提交前后的数据,数据被更改了 B update
幻读:A事务前后两次读取到B事务提交前后的数据数量,数据数量变了 B insert delete

SQL事务隔离级别


image.png

一般数据库默认值都是RC或者RR
RR按SQL要求无法避免幻读,但是innoDB避免了,使用了mvcc机制

mvcc multi-version concurrency control 多版本并发控制协议


image.png

读不加锁,读写不冲突,性能好。多个版本数据可以共存
原理:通过隐藏列和版本链实现的ReadView,打快照

  • D duration 持久性 事务一旦提交成功,对数据库的改变就是永久的 使用redolog实现持久化

你可能感兴趣的:(Mysql 学习)