6.MySQL高级优化
MySQL逻辑架构
1.连接层
2.服务层
3.引擎层
4.存储层
1.索引前言
SQL语句性能下降原因:
查询语句写的烂
索引失效
关联查询过多
。。。
SQL的执行顺序
Select <select_list> from leftTable join right on xx where xxx group by xx having xxx order by xxx limit xx
机器的执行顺序
from ——> join ...on ——>where ——>group by ——>having ——>select ——>order by ——>limit
七种JOIN理论
MySQL不支持全连接 full outer join 使用union
-- 等值连接 内连接(inner join) (取A表和B表的交集) 中间
select * from employee e inner join department d on e.depratment_id = d.department_id
select * from employee e ,department d where e.depratment_id = d.department_id
-- 左外连接 left outer join (取A表的全部和A表与B表的交集) 左一
select * from employee e left outer join department d on e.department_id = d.department_id;
-- 右外连接 right outer join (取B表的全部和 A表与B的交集) 右一
select * from employee e left outer join department d on e.department_id = d.department_id;
-- 只取(A表和B表并集中)A独有的 左二
select * from employee e left outer join department d on e.department_id = d.department_id where
d.department_id is null;
-- 只取(A表和B表并集中)B独有的 右二
select * from employee e right outer join department d on e.department_id = d.department_id where
e.department_id is null;
-- 取A表与B表的并集 MySQL中没有 full join 我们使用 union合并+去重 (两个左外连接进行union) 左三
select * from employee e left outer join department d on e.department_id = d.department_id
union
select * from employee e left outer join department d on e.department_id = d.department_id;
-- 取A和B的(并集-交集) 右三 (找出A独有的 B独有的 进行union)
select * from employee e left outer join departments d on e.department_id = d.department_id where
d.department_id is null
union
select * from employee e left outer join department d on e.department_id = d.department_id
where e.department_id is null
索引是什么
索引是一种数据结构 (B+Tree Hash) 可以简单理解为排好序的快速查找数据结构
一般来说索引本身也很大 不可能全部存储在内存中 因此索引往往以索引文件的形式存储在磁盘中
优势
提高数据检索效率 降低数据库的IO成本
通过索引对数据进行排序 降低数据排序的成本 降低CPU的消耗
劣势
- 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的
- 虽然索引大大提高了查询速度,同时却会降低更新表的速度,因为对表进行INSERT,UPDATE,DELETE操作时MySQL不仅要保存数据,还有保存索引文件信息
索引分类
主键索引
索引值必须唯一 不允许有null
唯一索引
索引列的值必须唯一 但允许有空值
普通索引
仅加速查询 无条件限制
全文索引
fulltext
单值索引
一个索引只包含一个列
复合索引
即一个索引包含多个列
聚簇索引和非聚簇索引
索引基本语法
创建索引
#建表时创建
create (unique|fulltext) index index_name on table_name (column1_name,...);
#建表后增加
alter table table_name add (unique|fulltext) index index_name (column1_name,...);
删除索引
drop index index_name on table_name;
查看索引
show index from table_name;
索引建立的情况
哪些情况需要建立索引
1.主键自动建立唯一索引
2.频繁作为查询条件的字段应该创建索引
3.查询中与其他表关联的字段,外键关系建立索引
4.查询中进行排序的字段,排序字段若通过索引去访问将大大提高排序速度
5.查询中统计或者分组字段
6.单值/组合索引的选择 (高并发下倾向于组合索引)
哪些情况不需要建立索引
1.表记录太少(300万记录MySQL性能开始下降)
2.where条件里用不到的字段不创建索引
3.经常增删改的字段不建立索引(不仅保存数据,还要保存索引文件)
4.如果某个字段列中包含许多重复的内容,为它建立索引就没有太大的效果(B+Tree的特性)
性能分析Explain
Explain关键字
能干什么?
1.查看表的读取顺序
2.数据读取操作的操作类型
3.那些索引可以使用
4.哪些索引被实际使用
5.表之间的引用
6.每张表有多少行被优化器查询
Explain + SQL语句
Explain select * from employee
字段解释
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1.id(表的加载顺序)
还有一种id是null的 union结果总是放在一个匿名临时表中,临时表不在SQL总出现,因此它的id是NULL。
1
##表示表加载的顺序 三种情况 第一种 id相同执行顺序 由上到下 顺序执行
select * from employee e left outer join departments d on e.department_id = d.department_id;
#说明deaprtment表先被记载 接着是employee表
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | d | null | ALL | PRIMARY | null | null | null | 4 | 100 | null |
1 | SIMPLE | e | null | ref | fk_employee | fk_employee | 5 | wsh.d.department_id | 3 | 100 |
2
## id不同 id越大优先级越高
select first_name from employee where salary > (select salary from employee where first_name = 'jack'
#子查询优先加载执行
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | employee | |||||||||
2 | SUBQUERY | employee |
2.select_type查询类型
1.SIMPLE :简单的select查询,查询中不包含union及子查询
2.PRIMARY :最外层的select查询 (不一定是子查询)
3.SUBQUERY: 子查询的第一个select查询,不依赖于外部查询的结果
4.DEPENDENT SUBQUERY: 子查询中的第一个select查询,依赖外部的查询结果
4.DERIVED: 在from后的子查询,MySQL会递归执行这些子查询,把结果放在临时表中
5.UNION : UNION 中的第二个或随后的 select 查询,不依赖于外部查询的结果集,若UNION包含在From子句的子查询中,外层select将被标记为DERIVED
6.UNION RESULT: 从UNION表中获取结果的select
3.type查询级别
显示查询使用了哪种类型 是较重要的一个指标
1.ALL : 全表扫描
2.index : 和ALL一样不同的就是mysql只需要扫描索引树 通常比ALL快一些
#例如
explain select count(*) from tableName;
3.range: 使用索引查询给定范围 (between and < > >=)等操作中可以
4.ref: 在表连接或者不连接的时候按照普通索引或者唯一性索引的部分前缀查找 可能会存在多条记录
5.eq_ref:进行连表查询的时候按照唯一性索引进行连接
6.const: 表示通过索引一次就找到了, const用于通过主键或者唯一索引查询,因为只匹配一行记录,所以很快,如将主键置于where列表中 MySQL就能将该查询转换为一个常量
7.system : 表仅有一行(系统表) 这是const的一个特例
8.null :
最好到最差依次 一般来说得保证查询至少达到**range**级别 最好能达到==ref==级别
system > const > eq_ref > ref > range > index > ALL
4.possiable_keys
- 这一列显示查询可能使用哪些索引来查找 不一定在查询的时候被使用
- explain 时可能出现 possible_keys 有列,而 key 显示 NULL 的情况,这种情况是因为表中数据不多,mysql认为索引对此查询帮助不大,选择了全表查询
- 如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查 where 子句看是否可以创造一个适当的索引来提高查询性能,然后用 explain 查看效果
5.key
-
这一列显示==mysql实际采用哪个索引==来优化对该表的访问。
-
如果没有使用索引,则该列是 NULL。如果想强制mysql使用或忽视possible_keys列中的索引,在查询中使用 force index、ignore index。
-
查询中如使用了覆盖索引 则该索引==仅==出现在key列表中 不出现在possiable_keys列表中
最多的情况就是你的某个字段建的有索引(前提是必须有索引),但是查询时后面不加where条件 这时进行全表扫描时 possible_keys为null 但是key却有值,type为index(表示全表扫描索引) 因为B+Tree的主键索引就是的叶子结点就是数据 其他索引叶子结点是主键索引值
所以可以不用全表扫描表 这时扫描索引即可查到数据 即覆盖索引 (包含数据的索引)
说明用到了索引 这种只适用于B+Tree索引结构
覆盖索引及优化:https://www.cnblogs.com/happyflyingpig/p/7662881.html
6.key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度,在不损失精确度的情况下,长度越短越好,精度越高,长度越长,key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的
7.ref
表示查找所用到的列或者常量 (const) 字段名
8.rows
表示MySQL进行本次查询==估计==要读取并检测的行数,注意这个不是结果集里的行数
值表示越少越好 查询次数少
9.Extra
包含不适合在其他列显示但又十分重要的额外信息
1.Using filesort 对查询产生副作用
索引的两个作用 :1.优化查询速度 2.优化排序速度和分组速度
出现了这个说明MySQL会对数据结果使用一个外部的索引排序,而不是按照我们创建的索引或者是(我们创建了索引但是排序的时候MySQL没有使用我们创建的索引(如组合索引右边的索引列) MySQL中无法利用索引完成的排序操作 称为’文件排序’
2.Using temporary 对查询产生副作用
使用了临时表保存中间结果,MySQL在对结果进行排序时使用了临时表,常见于order by 和分组查询 group by
3.Using index
覆盖索引
表明对表的请求列都是同一索引的部分的时候,返回的数据只使用了索引中的信息,而没有再去访问表中的记录(
也就是 (索引覆盖)),
例如 一张表有col1 col2 col3 字段 建了一个 组合索引 idx_col1_col2_col3
当查询的字段刚好是这三个字段时(任意组合) 即使后面不加where(或者是后面某些操作到导致索引失效了) 但是查询的时候也会用到索引,
就是直接从索引中取数据
4.Using where
表示查询使用了where条件
2.索引优化
1.优化案例一单表
# 查询语句
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY viewss DESC LIMIT 1;
分析可知进行了全表扫描 为category_id comments viewss 三列建立索引 但是 extra 里出现了 using filesort
根据组合索引的特性可知,comments使用了范围查询 会使排序的字段索引失效 所以我们只建 category_id viewss
这两例的索引,再次经过分析 发现排序字段使用了索引
组合索引的特性
现在为a b c 三个字段建立组合索引 idx_a_b_c(a,b,c)
B+Tree组合索引的特性
https://blog.csdn.net/cristianoxm/article/details/107818084
1、组合索引字段无论顺序如何改变都会用到索引,前提是所有字段都在where条件上。(使用and连接)
select * from tableName where a = xx and b = xx and c = xx
select * from tableName where c = xx and b = xx and a = xx
...
#只要全部字段全部用and连接 那么不管如何排列都会用到索引 MySQL的优化器会优化排序
2、如果想要使用一个或者两个字段在where条件上,必须有组合索引里的第一个字段,并且与顺序无关,例如a,c或c,a,这种场景是可以命中索引的。但是,b,c或c,b这种是不会命中索引的。
1.两个字段 必须有a字段 没有a字段不走索引 -- 如果a与a相邻的字段那么都会用到索引
-- 如果a与不与a相邻的字段 只有a走索引
select * from tableName where a = xx and b =xx -- b与a相邻 那么a b都走索引
select * from tableName where a = xx and b =xx
select * from tableName where a = xx and c =xx
select * from tableName where c = xx and a =xx -- c不与a相邻 c不走索引 a走索引
select * from tableName where b = xx and c =xx -- 没有a字段 不走索引
....
2.一个字段 只有单独使用a字段才走索引
select * from tableName where a = xx -- 使用索引的第一个字段 走索引
select * from tableName where b = xx -- 不走索引
select * from tableName where c = xx -- 不走索引
3、如果组合索引存在范围查询,则组合索引可能会命中索引,这个跟B+Tree的叶子节点中存储的数据是否在当前的叶子节点中,即InnoDB存储引擎的最小存储单元——页,InnoDB页的大小默认是16k,可以通过参数查看页的默认大小:show global status like ‘innodb_page_size’;如果想要修改InnoDB页的大小,需要通过修改mysql源码才可以修改,找到源码文件(storage/innobase/include/univ.i),找到参数:UNIV_PAGE_SIZE,该参数必须是2的n次方,例如4k、8k、16k、32k、64k等等。
2.优化案例二多表连接
select * from emp left outer join dept on emp.dept_id = dept.dept_id (emp.dept_id是外键 dept.dept_id是主键)
##使用explain分析 结果两张表都是ALL 并且都没用到索引
-- 优化
分析两张表的特性 可得员工表中员工数量(记录数)要远大于部门表的数量 即员工表的员工的部门id数肯定多于部门表
即在这种情况下 dept是小表(驱动表) emp是大表(被驱动表) 并且根据小表驱动大表的原则 即驱动表要在join左边,
被驱动表要在 left join右边
-- 左外连接的特性 取左表的全部数据 去扫描右表 所以我们的右表一定要建立索引
所以我们可以根据这两条进行优化
1)-- 在右表上建立索引 即大表建立索引
2)-- dept(小表)放在left join 左边 emp放在left join 右边
-- 优化后
1. select * from dept left outer join emp on emp.dept_id = dept.dept_id
可以发现 使用大表(被驱动表)到了索引 并且提升了type等级为ref
-- 进行内连接或者等值连接时 MySQL自动将大表作为被驱动表 只要大表上的连接字段有索引就行
3.索引失效
1.最佳左前缀规则 ==——>==组合索引的特性
2.不要在索引列上做任何操作**(计算,函数,(自动或手动类型转换**),会使索引失效而全表扫描
#name列有索引
explain select * from staffs where name = 'aa' -- 使用了索引
explain select * from staffs where SUBSTR(name ,2) = 'aa' -- 未使用索引 全表扫描 对索引列进行了函数操作
3.不能使用索引中的范围条件右边的列
#index idx_name_age_pos (name,age,pos) 组合索引
select * from staffs where name = 'aa' and where age > 12 and pos = 'manager'
age索引列使用范围查询 虽然三个索引字段都在where后 但是经过explain分析 只用到了name age两个索引 因为age进行了范围查询 会使后面的pos索引失效 -- 而age字段只进行了排序时使用了 查询未使用
4.尽量使用覆盖索引(要查询的字段与组合索引列保持一致) 减少 select * 的使用
#index idx_name_age_pos (name,age,pos) 组合索引
select * from staffs where name = 'aa' and age = '22' and pos = 'manager'
查询所有字段 Explain分析后 Extra里没有 Using index
-- 只查询索引字段
select name,age,pos from staffs where name = 'aa' and age = '22' and pos = 'manager'
经过分析 Extra里有Using index 说明直接从索引中拿数据 速度较快
5.MySQL在使用不等于<> !=时 所有索引会失效(组合索引情况下)
#index idx_name_age_pos (name,age,pos) 组合索引
select * from staffs where name != '12' and age = 21 and pos = 'manager'
经过分析 type 为ALL 全表扫描 不管是第几个索引使用了不等于 那么所有索引全部失效 进行全表扫描
注:
-- 单值索引不管有几个 一次查询MySQL只使用一个 所以这时我们就需要组合索引
#现在我们有三个单值索引
idx_name(name) idx_age(age) idx_pos(pos)
因为一次查询MySQL只会选择一个单值索引选择使用 在我们有使用了不等于的情况下,
-- MySQL只是不会选择那一列当做查询时的索引 而会选择其他未使用不等于作为查询条件的索引列做为索引
select * from staffs where name != '12' and age = 21 and pos = 'manager'
经过Explain分析 使用了age作为索引
6.is not null无法使用索引 但是is null可以使用索引
7.like通配符 左模糊或者全模糊都无法使用索引
中间索引失效 会导致组合索引后面的索引全部失效
8.字符串不加单引号索引失效 底层进行了数据类型转换,使用了函数
9.少用or 用它连接会使索引失效
#index idx_name_age_pos (name,age,pos) 组合索引
select * from staffs where name = '' or age = 21 or pos = 'manager' -- type=ALL 索引失效 全表扫描
-- 单值索引情况下使用or查询 type = ALL 索引失效 全表扫描
select * from staffs where age = 21 or age =22 -- 一列的or查询 也会造成索引失效
10.单值索引进行范围查找索引会失效
小总结
现有组合索引 idx_a_b_c(a,b,c)
where 语句 | 索引是否被使用引 | 使用了哪个列的索引 | 原因 |
---|---|---|---|
where a = 3 | Y | a | 组合索引单个使用只能使用最左边 |
where a = 3 and b =5 | Y | a b | 组合索引使用两个只能使用最左边和相邻的 |
where a = 3 and b = 5 and c = 4 | Y | a b c | 组合索引全部使用那么全部都走索引 |
where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 | N | null | or连接使索引全部失效 |
where a = 3 and c = 5 | Y | a | 组合索引使用最左和不相邻的 那么只能使用最左的 |
where a = 3 and b > 4 and c = 5 | Y | a b | b进行范围查询 使c索引失效 |
where a = 3 and b like ‘kk%’ and c = 4 | Y | a b c | like 匹配右后缀 索引不会失效 |
where a = 3 and b like ‘%kk’ and c = 4 | Y | a | like 匹配左前缀 索引失效 导致c也失效 |
where a = 3 and b like ‘%kk%’ and c = 4 | Y | a | like 匹配全模糊 索引失效 导致c也失效 |
where a = 3 and b like ‘k%kk%’ and c = 4 | Y | a b c | like 匹配右后缀 索引不会失效 |
**中间索引失效 会导致组合索引后面的索引全部失效 **
例如中间索引使用了 like全模糊或左模糊 进行范围查询等 都会使后面索引失效 但是进行范围查询可以使用索引,
但是是进行排序使用
4.面试题
脑图
3.查询截取分析
- 1.观察,至少跑一天,看看生产的慢SQL情况
- 2.开启慢查询日志,设置阙值,比如超过5秒钟的就是慢SQL,并抓取出来
- 3.explain + 慢SQL分析
- 4.show profile
- 5.进行SQL数据库服务器的参数调优(运维orDBA来做)
1.in和exists 小表驱动大表
查询出在部门表的里面的员工 (使用子查询) 分析可知 要使用department_id作为条件进行匹配 并且employee的数据集远大于deartments 这时employee就是大表 departments就是小表
-- 使用in
select * from employee where department_id in (select department_id from departments)
-- 使用exists
select * from employee e where exists (select 1 from department_id d where e.department_id = d.department_id)
经过分析 在这种情况下in的速度要快于exists
-- 当大表在前匹配小表的内容时 使用exists
-- 当小表在前匹配大表的内容时 使用in
注意: 条件字段需要建立索引
2.order by字段排序优化
使用order by排序 尽量使用索引排序 避免出现FileSort的方式
补充:
-- 注意: 顺序一定不能乱 且升序降序要一致 还有就是 如果查询的字段中的列比你索引的字段多1 这是只能是所有的索引
-- 全上才能不出现 filesort 如果表中字段非常多 且你的组合索引字段很少 那么排序时是不可能使用到索引的
-- 这就是为什么不建议写 select * 的原因 给那些字段排序就select 哪些字段 相同的在查询的时候也是
-- 尽量不要使用select * 如果我们只写索引的字段 就算是我们的索引可能失效了 但是还有覆盖索引 直接从索引树上取数据
使用三个 一定是 order by a b c
使用两个 一定是 order by a b
使用一个 一定是 order by a
where a = const order by c ,b -- 这种不会用到索引 虽然索引的最左前缀定义为了常量 但是后面的顺序变了
where a = const order by a,b,c -- 这种也不会使用索引 where后的条件等于常量值的索引列排序的时候再次使用的话就会造成出现filesort
where a = const order by a -- 会出现filesort 排序时索引失效
where a = const and b > const order b,c -- 不会出现索引失效 a b c
where a = const and b > const order c -- c排序索引失效 因为b使用了范围查询 断了 c就用不上
注: -- 为何前面的前缀的索引定义为常量时可以使用索引排序
这跟B+Tree有关系 在第三层中 最前缀的索引是排好序的 后面的索引根据最前缀索引值是否相同排序
所以说当 前面匹配的值为常量时 如果有相同的B+Tree已经排好了下一个索引数据的顺序
如果不在索引列上排序,filesort会有两种算法
双路排序和单路排序
-
https://blog.csdn.net/yanghao8866/article/details/107796487
2.阳哥脑图
3.group by的优化
1.group by的实质是先排序后分组 遵守索引的最佳左前缀(与 order by规则一致)
2.当无法使用索隐列,增大max_length_for_sort_data 参数的设置 + 增大sort_buffer_size的设置(跟order by一样)
3.where 高于 having 能在where后跟查询条件的就不要去having限定
4.慢查询日志
阳哥脑图
5.批量数据脚本
6.Show Profile
7.全局查询日志
生产环境不要开
4.MySQL锁机制
1.锁的分类
- 从对数据操作的类型 分为共享锁(读锁) 排他锁(写锁)
共享锁:针对同一份数据 多个读操作可以同时进行而不会互相影响
排它锁: 当前写操作没有完成前,它会阻断其他写锁和读锁
- 从对数据操作的粒度 分为表锁和行锁
1.为表加读锁(表级读锁)
1.加表锁
lock table tableName (read|write) -- 读锁或写锁
2.解锁
unlock tables;
Connection - 1 | Connection - 2 |
---|---|
1.为表mylock加表级读锁 lock table mylock read | 2.select * from mylock;可以查出记录 |
3.select * from mylock;可以查出记录 | 4.select * from dept 本库中的另一张表 可以查出记录 |
5.select * from dept 无法查询 报错 | 6.update dept 可以更新 |
7.update mylock 无法更新 报错 | 8.update mylock 无法更新 被阻塞 |
9.unlock tables 解锁 | 9. 阻塞状态瞬间结束更新成功 如果一直不解锁 等待时间过长报错执行失败 |
总结表级读锁:表级读锁 A连接锁住了T表
1.A连接只能读取T表 不能写T表(报错) 不能读写任何其他表
2.B连接只能读取T表 不能写T表(阻塞状态 等待A连接释放锁立刻执行写操作 如果A连接不解锁 等待时间过长报错执行失败) 可以读写其他表
2.为表加写锁(表级写锁)
connection-1 为mylock表加了表锁后 connection-1可以读写本表 不能读写其他表 connection-2读 写被锁的表都需要阻塞等待 可以读写其他表 待connection-1释放锁之后才能读写
3.表锁总结
- 简而言之 就是读锁会阻塞所有的(包括上锁的连接)写 所有连接可以一起读
- 写锁阻塞不包括本连接在内的所有的读写 本连接可以读写
- 上锁的连接只能操作被自己锁住的表 不能操作其他表
show open tables;查看那些表被锁了
show status like 'table%'
table_locks_immdeiate 表示表级锁定的次数 表示可以立即获取锁的查询次数,每立即获取锁 值加一
table_locks_waited 出现表级锁定争用而发生等待的次数(不能立即获取锁的次数,每等待一次锁值加一) 此值高说明存在着较严重的表级锁争用情况
MyISAM不适合做写为主的表引擎,因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难获得锁,从而造成永远阻塞 MyISAM只有表锁 InnoDB不仅有表锁还有行锁
2.行锁
InnoDB与MyISAM最大的不同点: 1. InnoDB支持事务 2.InnoDB有行锁
行锁支持事务
A在事务中更新了T1表中的一条记录 ,就为这条记录加上了行锁,其他连接只能读这条记录,写==这条==记录要阻塞
等待A连接的提交 但是对其他行的记录可以读写
行锁失效
我们在演示上面的行锁的时候 update xxx where 后面都是有索引的列 如果我们update xxx where时条件不是索引列
或者是索引列但是我们因为某些操作导致索引失效那么就会把行锁变为表锁
也就是说 行锁的前提是where条件要是索引列 所以我们在更新的时候最好按照主键来更新
3.使用select进行加锁
前提知识: 排它锁 与互斥锁 https://blog.csdn.net/diaobatian/article/details/90603887
MySQL默认的delete update insert语句都会给涉及到的数据加 排它锁(互斥锁)
简单的查询语句默认不加任何锁 想要加锁
- 加共享锁 后面加 lock in share mode
- 加排它锁 后面加 for update
1、如何理解共享锁与排它锁
- 共享锁很好理解,就是多个事务只能读数据不能改数据
- 排它锁:对于排他锁大家的理解可能就有些差别,以为排他锁锁住一行数据后,其他事务就不能读取和修改该行数据,其实不是这样的。排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁
2、lock in share mode / for update 锁
1.A事物对表T1中的一条数据加了共享锁 如果说这时没有其他事物对这条数据加共享锁 那么A事务是可以为这条数据加排它锁(也就是可以写这条数据) 一旦加上了排它锁 那么其他事物就无法在为这条数据加任何的锁 只能等待A释放锁
2.如果这时已经有事务对这条数据加了共享锁 那么这时所有的(包括A事务)事务也无法在为这条数据加排它锁(即写操作),只能等待其他已经为这条数据加上了共享锁的事务释放锁(提交事务)后,也就是除了自己其他所有加了共享锁的事务全部提交之后才能对这条数据进行写(加排它锁)
共享锁的本质就是 : 先有一条数据DataA 大家(事务)都想访问他,但是怕大家在访问的过程中数据可能不一致,大家都商量着加锁 谁想访问这条数据 就给他上锁(共享锁) 规定只要数据上了锁(共享锁)就不允许任何人改动,想改可以,等我们全部都访问完成之后(事务提交),或者是我们都完成访问了这个数据没有共享锁了 或者是只剩下一个人了 而这个人还没提交 这时候有且只有(即本事物)这个人可以修改(加排它锁) 这就是共享锁的含义 也就是说只有共享锁的数量大于等于2 那么任何人都不得加排它锁 共享锁的数量等于1的时候 共享锁是谁的 谁可以写(加排它锁) 其他任何人不准加排它锁 (要加就要排队阻塞)
A事务{
begin;
select * from mylock where id =1 lock in share mode; -- 为id为一的数据加共享锁
-- select * from mylock where id =1 for yyodate
select * from mylock where id = 1 ; -- 可读
update mylock set name = '12' where id = 1; -- 在本事物内在加排它锁是可以的前提是 (这时候没有其他事物加共享锁) 其他事务中只能加共享锁 -- 注意这里的查询条件一定要有索引 不然行锁变表锁
commit;
}
当A事务率先给这条数据加了共享锁后
后续的加锁情况分下面几种
1.如果A事务没有继续加排它锁 那么其他事物 -- 只可以加共享锁
2.如果A事务这时继续加了排它锁 那么其他事物不可以继续加任何锁 其他事物要想加锁必须阻塞等待
3.如果其他事物已经加了共享锁 那么这时A也不能加排它锁 要加必须阻塞等待
当A事务给一条数据加上了排它锁之后 更新 删除 插入默认都加排它锁 查询要显示的加 for update
{
select * from mylock where id = 1 for update -- 加了排它锁
}
其他事物在A事务没有提交之前不准在为这条数据加任何锁 共享锁/排它锁
4.间隙锁的危害
行锁变表锁 (脑图)
5.乐观锁
数据不安全 要明白加锁是为了什么 数据安全性
比如:先有一个数据等于data = 1 多个事务来了 都想更新 假设事务A 先读取了data = 1,然后准备更新data = 2,
由于某些原因事务A卡顿了,这时刚好事务B来了 直接更新了最新的数据data = 3,等事务B提交了data = 3,这时事务A
继续执行 data = 2,把最新的数据覆盖了,得到的是旧数据,这时就产生了问题 最新的数据被卡顿的事务覆盖了,
出现这种事情的原因就是 我事务A在读取的时候卡顿了,我还没执行更新操作呢,你事物B来了,你更新完这时是最新数据,但是我事务A不知道啊,我继续着我的更新操作,结果就是我把最新的数据覆盖了,主要原因就是事务A操作不是一个原子性,即我在进行操作的时候这行数据没有被锁,导致你后来的事务改了数据,但是我事务A不知道,要解决这个问题, 就是加锁(必须是排它锁) 即事务A在操作这行数据的时候其他事务不能动这行数据 我更新完了 你们随意动
这时我们就想起了 加锁机制 但是MySQL中什么锁可以避免我先来的准备改数据时,你后来的不能动数据,-- ==排它锁=
加了排它锁之后 事务A开启事务保证本事物内的各种操作的原子性,事务A通过select xx for update 或者直接update 对这一行数据进行加排它锁,在A事务没有释放锁的这段时间,B事务想要改数据只能阻塞等待A释放锁才能进行修改,这样一来,我们就保证了数据的正确性,不会出现上述我们在读取数据的时候还没来的及更新 其他事物就已经把我们的数据更新了,导致最后我们覆盖了最新的数据, 但是大大的降低了并发性,因为你锁了这一行,事务提交之前其他事物都不准碰数据,可能你这个事务里面第一行就是更新的操作,其他的操作都跟这行无关,但是其他事务还是得等到你释放锁
2.加乐观锁
我们知道加锁会大大的降低系统的并发,由此我们也通过乐观锁机制来控制数据的正确性,
假设还是上面的例子 ,我们设置一个版本号,每次对数据进行更新的时候都先去读取version,更新的时候对比version,一次来判断我们更新的数据是否是基于我们一开始读取的数据 避免我们覆盖其他事务的数据
具体流程就是 先假设这时version = 1 假设A事务先读取了版本号 version = 1 ,然后准备进行对比版本号更新时,事务A出现了卡顿,这时B事务来了,也读取version = 1,B事务直接更新了数据,并把version的值变为2,这时A准备对比version进行更新,但是发现version不一致了,也就对数据更新失败,即在这段时间里有后来的事务已经对数据完成了更新,也就不会覆盖最新的数据,乐观锁带了锁 是加上并不是真正的锁,并发性高,不用对数据加锁
https://blog.csdn.net/weixin_36012152/article/details/113602500
乐观锁是多个事务操作同一条数据,在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚 就是说在一次事务中,读出了版本号是1,然后想要进行更新,由于网络等一些问题,速度慢,这时另一个事务已经提交了这次数据 把版本号也变了 你在去更新对比版本号发现不一致就更新失败 保证了数据是最新数据
乐观锁跟 悲观锁是不一样的
悲观锁是多个事务操作同一条数据 在过程中就锁定这条数据 保证其他事务不准修改数据 并发性弱
乐观锁是在提交结果的时候 进行版本比对 保证修改的数据在此时还没有被其他事物修改 即确定读到的数据与提交更新时版本号如果被修改过 那么就回滚,
并发性强
两种实现方式
- 加版本号
ion = 1 ,然后准备进行对比版本号更新时,事务A出现了卡顿,这时B事务来了,也读取version = 1,B事务直接更新了数据,并把version的值变为2,这时A准备对比version进行更新,但是发现version不一致了,也就对数据更新失败,即在这段时间里有后来的事务已经对数据完成了更新,也就不会覆盖最新的数据,乐观锁带了锁 是加上并不是真正的锁,并发性高,不用对数据加锁
https://blog.csdn.net/weixin_36012152/article/details/113602500
乐观锁是多个事务操作同一条数据,在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚 就是说在一次事务中,读出了版本号是1,然后想要进行更新,由于网络等一些问题,速度慢,这时另一个事务已经提交了这次数据 把版本号也变了 你在去更新对比版本号发现不一致就更新失败 保证了数据是最新数据
乐观锁跟 悲观锁是不一样的
悲观锁是多个事务操作同一条数据 在过程中就锁定这条数据 保证其他事务不准修改数据 并发性弱
乐观锁是在提交结果的时候 进行版本比对 保证修改的数据在此时还没有被其他事物修改 即确定读到的数据与提交更新时版本号如果被修改过 那么就回滚,
并发性强
两种实现方式
- 加版本号
- CAS
5.MVCC
5.1MVCC概述
全称 Multi-Version Concurrency Control ,即多版本并发控制,MVCC是一种并发控制的方法,在InnoDB引擎中的实现主要是为了提高数据库的并发性能
5.2当前读和快照读
当前读: 即加锁读
select xxx lock in share mode(共享锁) selectxx for update ,update insert delete这些都是一种当前读,为什么叫当前读,就是读取的记录是最新版本,读取时还要保证其他事物不能修改当前记录,会对读取的记录进行加锁
快照读:
像不加锁的select操作就是快照读,即不加锁的非阻塞读,前提是数据库的隔离级别不能Serializable(串行化),串行级别下的快照读会退化成当前读,之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC
可以认为MVCC是行锁的一个变种,但是在很多情况下避免的加锁的操作,降低了开销,既然是基于多版本,及快照读可能读取到的版本并不一定是数据的最新版本,而有可能是之前的历史版本
。。。。
undo日志 回滚指针 事务id
RR(读已提交)如何在RC(读已提交)的基础上解决不可重复读
主要原因就是生成ReadView的时机不同,RR生成ReadView是已一个事务为单位,即一个事务的所有读共用一个ReadView,这个ReadView是第一次select查询时生成的,
而RC是每一条select语句都会生成一个ReadView,这也就是为什么在一个事务读取相同的数据可能发生变化的原因