Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
23 views

notes 图解mysql

notes-图解mysql

Uploaded by

zunlin ke
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
23 views

notes 图解mysql

notes-图解mysql

Uploaded by

zunlin ke
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 52

notes-图解mysql.

md 4/29/2022

安装配置mysql

apt serach mysql


apt -y install mysql-server
# root,123456

which mysqld
/usr/sbin/mysqld

#查看mysql版本: 5.7.
mysql --version
mysql Ver 14.14 Distrib 5.7.33, for Linux (x86_64) using EditLine wrapper

netstat -napt | grep mysqld


tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN
31292/mysqld

ps -aux | grep mysqld


mysql 31292 0.0 0.2 1180020 140256 ? Ssl 08:46 0:00 /usr/sbin/mysqld

# 修改mysql监听ip从127.0.0.1--->0.0.0.0开启远程登录访问
vim /etc/mysql/mysql.conf.d/mysqld.cnf
#bind-address = 127.0.0.1
bind-address = 0.0.0.0

service mysql restart


netstat -napt | grep mysql
tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN
31628/mysqld

# 修改mysql默认编码从latin1--->utf8便于⽀持中文

mysql -uroot -p
> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
> show variables like '%char%';
> create database test;
> use mysql;
> SELECT host FROM mysql.user WHERE user = "root";
+-----------+
| host |
+-----------+

1 / 52
notes-图解mysql.md 4/29/2022

| localhost |
+-----------+
1 row in set (0.00 sec)

> GRANT ALL ON test.* to 'root'@'192.168.2.41' IDENTIFIED BY 'xxx';


> FLUSH PRIVILEGES;
> SELECT host,user FROM mysql.user;
+--------------+------------------+
| host | user |
+--------------+------------------+
| * | * |
| 192.168.2.41 | root |
| localhost | debian-sys-maint |
| localhost | mysql.session |
| localhost | mysql.sys |
| localhost | root |
+--------------+------------------+
6 rows in set (0.00 sec)

# remote connect
mysql -uroot -p123456 -h192.168.0.18 test
> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| test |
+--------------------+
2 rows in set (0.00 sec)

常⽤命令

// 登录
mysql -uroot -p

// 关闭、启动、重启
[sudo] service mysql stop
[sudo] service mysql start
[sudo] service mysql restart
// 退出
mysql> exit
mysql> quit

// 查看所有db文件
mysql> show databases;
// 选择db文件
mysql> use name;
// 创建db文件
mysql> create database name

2 / 52
notes-图解mysql.md 4/29/2022

// 删除db文件
mysql> drop database name

//// 表操作

// 列出所有表
mysql> show tables

// 创建⼀个名为t_name的新表:
create table t_name(
id int(10) not null auto_increment primary key,
name varchar(40),
pwd varchar(40)
) charset=gb2312;

// 删除名为t_name的数据表
drop table t_name

// 显⽰名为t_name的表的数据结构
describe t_name

show columns from t_name

// 将表t_name中的记录清空
delete from t_name

// 显⽰表t_name中的记录
select * from t_name

// 复制表结构
mysqldump -uUSER -pPASSWORD --no-data DATABASE TABLE > table.sql

// 更改表得的定义把某个栏位设为主键
ALTER TABLE t_name ADD PRIMARY KEY (col_name)

// 把主键的定义删除
ALTER TABLE t_name DROP PRIMARY KEY (col_name)

// 在t_name表中增加⼀个名为col_name的字段且类型为varchar(20)
alter table t_name add col_name varchar(20)

// 在t_name中将col_name字段删除
alter table t_name drop col_name

// 修改字段属性,注若加上not null则要求原字段下没有数据
alter table t_name modify col_name varchar(40) not null
(注,SQL Server200下的写法:Alter Table t_name Alter Column col_name varchar(30)
not null)

// 修改表名:
alter table t_name rename to new_tab_name

3 / 52
notes-图解mysql.md 4/29/2022

// 修改字段名:
alter table t_name change old_col new_col varchar(40)
(注:必须为当前字段指定数据类型等属性,否则不能修改)

// ⽤⼀个已存在的表来建新表,但不包含旧表的数据
create table new_t_name like old_t_name

//// 数据备份与回复:

执⾏外部的sql脚本:

当前数据库上执⾏:mysql < input.sql


指定数据库上执⾏:mysql [表名] < input.sql

数据传入命令: load data local infile "[文件名]" into table [表名]

备份数据库:(dos下)
mysqldump --opt school>school.bbb
mysqldump -u [user] -p [password] databasename > filename (备份)
mysql -u [user] -p [password] databasename < filename (恢复)

mysql索引失效的常⻅场景?
index存储结构
mysql默认引擎,InnoDB存储引擎, 使⽤B+Tree Index; 创建Table时,InnoDB会默认创建clustered index
MyISAM存储引擎,⽀持B+Tree Index,R Tree Index,Full-text Index; 创建Table时默认使⽤B+Tree index

table(id,name,age,address)

innodb vs myisam B+Tree index:

innodb: B+tree index的leaf node存储primary key + data itself

4 / 52
notes-图解mysql.md 4/29/2022

myisam: B+tree index的leaf node存储primary key + data physical address

clustered index vs secondary index:

clustered index: 聚集索引, 只有1个, leaf node存储primary key+data (id,age,name)


secondary index: ⼆级索引, 可以有N个, leaf node存储secondary index+primary key (name,id)

5 / 52
notes-图解mysql.md 4/29/2022

union index: ⼆级索引,可以有多个,(a,b,c)多个普通字段组合在⼀起,需要遵循 最左匹配原则

索引 B+ 树是按照「index value」有序排列存储的,只能根据前缀prefix进⾏比较。

InnoDB 在创建clustered index时,会根据不同的场景选择不同的列作为索引:

如果有primary key,默认会使⽤primary key作为clustered index的索引键;


如果没有primary key,就选择第⼀个不包含 NULL 值的unqiue列作为clustered index的索引键;
在上⾯两个都没有的情况下,InnoDB 将⾃动⽣成⼀个hidden auto_increment id 列作为聚簇索引的索引
键;

回表 vs 索引覆盖

回表, 先查找secondary index得到id, 然后回表查找clustered index得到data; 查找2个B+ Tree


索引覆盖(covering index),查找secondary index得到id, 查找1个B+ Tree

如何选择index+回表?

// id 字段为主键索引
select * from t_user where id=1;
1. 从clustered index中检索leaf node得到所有字段(id,name,age,address)

// name 字段为⼆级索引
select * from t_user where name="林某";
1. 从secondary index中检索leaf node获取primary key(id)
2. 根据primary key(id)从clustered index中检索leaf node得到所有字段
(id,name,age,address)
此过程 [回表]

index失效导致全表扫描
对索引使⽤%xxx或者%xxx%模糊匹配

索引 B+ 树是按照「index value」有序排列存储的,只能根据前缀prefix进⾏比较。
6 / 52
notes-图解mysql.md 4/29/2022

// name 字段为⼆级索引
select * from t_user where name like '%林';
select * from t_user where name like '%林%';

type=ALL 就代表了全表扫描,⽽没有⾛索引。

// name 字段为⼆级索引
select * from t_user where name like '林%';
1. type = range, key = index_name, Extra = Using index condition ⾛了secondary
index扫描得到id
2. 得到id之后在进⾏clustered index扫描,最终获取全部数据(id,name,age,address)

对index使⽤function

// name 为⼆级索引
select * from t_user where length(name)=6;
type=ALL 就代表了全表扫描,⽽没有⾛索引。

// 8.0之后使⽤function index
alter table t_user add key idx_name_length ((length(name)));
select * from t_user where length(name)=6;
type = ref, key = idx_name_length 使⽤了index

因为索引保存的是索引字段的index value,⽽不是经过函数计算后的value,⾃然就没办法⾛索引了。
从8.0以后增加了function index,可以对field进⾏function计算后的value建立index

对index进⾏expression计算

select * from t_user where id + 1 = 10;


type = ALL代表全表扫描

select * from t_user where id = 10 -1;


type = const, key = PRIMARY ⾛了clustered index

对index进⾏隐式cast

mysql数据类型转换规则

mysql> select "10" > 9;


+----------+
| "10" > 9 |
+----------+
| 1 |

7 / 52
notes-图解mysql.md 4/29/2022

+----------+
1 row in set (0.00 sec)

mysql在遇到string和interger比较的时候,会将string⾃动转换为integer

// 增加⼀个phone varchar(30);
select * from t_user where phone = 1300000001;
===>等价于
select * from t_user where CAST(phone AS signed int) = 1300000001;
type= all

select * from t_user where id = "1";


===>等价于
select * from t_user where id = CAST("1" AS signed int);
type = const, key = PRIMARY

union key非最左匹配

// (a,b,c) 最左匹配原则
// a, (a,b)/(b,a), (a,b,c)/(a,c,b)/(b,a,c)/(b,c,a)/(c,a,b)/(c,b,a)
where a = 1;
where a = 1 and b = 2;
where b = 2 and a = 1;
where a = 1 and b = 2 and c = 3;
where c = 3 and b = 2 and a = 1;

type = ref, key = index_abc 使⽤union key

对于(id, a,b,c), id是primary key, abc是union key


select * from t where a = 1; 符合最左匹配: type=ref, key = index_abc, Extra = Using
where;Using index
select * from t where c = 3; 不符合最左匹配,但是可以索引覆盖: type=index, key =
index_abc, Extra = Using where;Using index

where a = 1 and c = 3 ,符合最左匹配吗?


type = ref, key = index_abc, Extra = Using index condition
索引截断,不同mysql版本有不同的处理⽅式
1. MySQL 5.5 的话,前⾯ a 会⾛索引,在联合索引找到主键值后,开始回表,到主键索引读取数据
⾏,然后再比对 z 字段的值。
2. 从 MySQL5.6 之后,有⼀个索引下推功能,可以在索引遍历过程中,对索引中包含的字段先做判
断,直接过滤掉不满⾜条件的记录,减少回表次数。

where字句种的or

// or前是primary key, or后age普通字段


select * from t_user where id = 1 or age = 18;
type = ALL,key= null 全表扫描
8 / 52
notes-图解mysql.md 4/29/2022

// 为age创建secondary index
select * from t_user where id = 1 or age = 18;
type = index_merge,key= PRIMARY,index_age, Extra = Using union(PRIMARY,index_age)
⾛索引扫描
index_merge表⽰对id和age分别进⾏索引扫描,然后merge结果

4个模糊查询题⽬
题⽬1:⼀个表有多个字段,其中 name 是索引字段,其他非索引,id 拥有⾃增主键索引。
题⽬2:⼀个表有2个字段,其中 name 是索引字段,id 拥有⾃增主键索引。

上⾯两张表,分别执⾏以下查询语句:

select * from s where name like "xxx";


select * from s where name like "xxx%";

select * from s where name like "%xxx";


select * from s where name like "%xxx%";

针对题⽬ 1 和题⽬ 2 的数据表,哪些触发索引查询,哪些没有?

对于题⽬1: (id,name,other,...)

clustered index: PRIMARY(id, name, other,...)


secondary index: index_name(name, id)

select * from s where name like "xxx";


select * from s where name like "xxx%";
s1,s2: type = range, key = index_name, Extra = Using index condition
⾛index_name⼆级索引得到id之后进⾏回表

select * from s where name like "%xxx";


select * from s where name like "%xxx%";
s3,s4: type = ALL, Extra = Using where
⾛全表扫描

对于题⽬2: (id,name)

clustered index: PRIMARY(id, name)


secondary index: index_name(name, id)

(id,a,b,c) PRIMARY,index_abc

select * from s where name like "xxx";


select * from s where name like "xxx%";
s1,s2: type = range, key = index_name, Extra = Using where; Using index

9 / 52
notes-图解mysql.md 4/29/2022

⾛index_name⼆级索引, Using index表⽰使⽤了索引覆盖, ⽆需扫描clustered index


(type=range说明利⽤了prefix进⾏range匹配)

select * from s where name like "%xxx";


select * from s where name like "%xxx%";
s3,s4: type = index, key = index_name, Extra = Using where; Using index
⾛index_name⼆级索引, Using index表⽰使⽤了索引覆盖, ⽆需扫描clustered
index(type=index表⽰全扫描遍历secondary index的B+Tree)

type=range 的查询效率会比 type=index 的⾼⼀些

s3,s4为什么选择全扫描⼆级索引树,⽽不扫描全表(聚簇索引)呢? 因为⼆级索引树的记录东⻄很少,就只有
「索引列+主键值」,⽽聚簇索引记录的东⻄会更多.
相同数量的⼆级索引记录可以比聚簇索引记录占⽤更少的存储空间,所以⼆级索引树比聚簇索引树⼩,这样遍
历⼆级索引的 I/O 成本比遍历聚簇索引的 I/O 成本⼩,因此「优化器」优先选择的是⼆级索引。

1. 如果数据库表中的字段只有主键+⼆级索引(id,name),那么即使使⽤了左模糊匹配(name like %xxx),也


不会⾛全表扫描(type=all),⽽是⾛全扫描⼆级索引树(type=index)。
2. 如果数据库表中的字段只有主键+union key(id,a,b,c), 那么即使不满⾜最左匹配(where c=3),也不会⾛全
表扫描(type=all), ⽽是⾛全扫描⼆级索引树(type=index)

where name like %xxx 和 where c=3

通常情况下⾛全扫描(type=all);
如果能够索引覆盖(id,name)/(id, a,b,c), 那么⾛全扫描⼆级索引树(type=index, key =
index_name/index_abc, Extra = Using where;Using index)

count(*)性能最差?
哪种 count 性能最好?
结论: count(*) = count(1) > count(primary key) > count(normal field)

count() 是⼀个聚合函数,函数的参数不仅可以是字段名,也可以是其他任意表达式,该函数作⽤是统计符合查
询条件的记录中,函数指定的参数不为 NULL 的记录有多少个。

mysql server会维护⼀个count变量, server 层会循环向 InnoDB 读取⼀条记录,如果 count 函数指定的参数不为


NULL,那么就会将变量 count 加 1,直到符合查询的全部记录被读完,就退出循环。最后将 count 变量的值发
送给客户端。

select count(name) from t_order;


这条语句是统计「 t_order 表中,name 字段不为 NULL 的记录」有多少个。也就是说,如果某⼀条
记录中的 name 字段的值为 NULL,则就不会被统计进去。

select count(1) from t_order;


统计「 t_order 表中,1 这个表达式不为 NULL 的记录」有多少个。实际上是统计t_order表有多少
条记录

count(primary key) 执⾏过程是怎样的?

10 / 52
notes-图解mysql.md 4/29/2022

select count(id) from t_order;


1. 如果没有secondary index,那么type = index, key = PRIMARY⾛聚集索引
2. 如果有secondary index,那么type = index, key = index_order⾛⼆级索引, 并且选择
key_len最⼩的那个secondary index
聚集索引和⼆级索引都能够使⽤的时候,优先使⽤⼆级索引,IO成本更⼩

select count(1) from t_order;


和 select count(id) from t_order;类似
唯⼀区别是count(id)遍历的时候需要读取id的value,count(1)遍历的时候不需要读取字段的value
count(1)比count(id)少了⼀个步骤,性能⾼⼀些

count(*) = count(0) = count(1)


InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way.
There is no performance difference.

select count(name) from t_order;


1. name普通字段, type = all全表扫描
1. name⼆级索引, type = index, key = index_name⼆级索引扫描

总结

count(*)/count(0)/count(1)、 count(主键字段)在执⾏的时候,如果表⾥存在⼆级索引,优化器就
会选择key_len最⼩的⼆级索引进⾏扫描。
如果要执⾏ count(*)/count(0)/count(1)、 count(主键字段)时,尽量在数据表上建立⼆级索引,这
样优化器会⾃动采⽤ key_len 最⼩的⼆级索引进⾏扫描,相比于扫描主键索引效率会⾼⼀些。
不要使⽤ count(普通字段)来统计记录个数,因为它的效率是最差的,会采⽤全表扫描的⽅式来统计

innodb vs myisam count(*)

innodb⽀持事务,MVCC, count(*)需要遍历所有记录
myisam 通过meta信息存储raw_count,由table lock保持⼀致性,因此count(*)时间复杂度是O(1),⽆需遍历
所有记录

如何优化 count(*)?
如果对⼀张⼤表经常⽤ count() 来做统计,其实是很不好的。 比如下⾯我这个案例,表 t_order 共有 1200+ 万
条记录,我也创建了⼆级索引,但是执⾏⼀次 select count() from t_order 要花费差不多 5 秒!
第⼀种,近似值 explain select count(*) from t_order; 得到近似值rows,但是只需要4ms
第⼆种,额外表保存真实值,查询很快,但是有维护成本

Tree的演化(索引为什么能提升性能?)
对n个元素进⾏search

扫描法: O(N)
binary search(⼆分查找): 排好序然后search, O(logN); ⼀次排序,多次search

Tree

11 / 52
notes-图解mysql.md 4/29/2022

Binary Tree ⼆叉树


Binary Search Tree(BST)
left < root < right
⼆叉搜索树(元素已经排序), 常规O(logN)
最差情况退化成⼀个链表,O(N)

AVL Tree (Self-Balancing BST)


每个节点的左⼦树和右⼦树的⾼度差不能超过 1
最坏情况O(logN)
insert node的时候通过leftRotate,rightRotate调整balance

Red-Black Tree
red-black 规则
query性能比AVL Tree差; insert/delete性能比AVL Tree好

Red-Black的应⽤

C++ map,multimap,multiset
Linux process scheduling算法CFS(完全公平调度)使⽤R-B tree来存储PCB
epoll使⽤R-B tree来存储socket fd

B Tree (balanced multi-way search tree)


是BST的扩展,允许m个⼦节点 (m>2),从⽽降低树的⾼度
每⼀个节点最多m-1个数据
logm(N), m是阶数(m=3),N是节点数
提供比log(N)更好的性能
⽤于database,filesystem

B+ Tree

12 / 52
notes-图解mysql.md 4/29/2022

m = 4; 允许4个⼦节点,每个node最多存储3个index, 4个pointer;

B+ 树就是为了拆分索引数据(index)与业务数据(data)的平衡多叉树

disk IO非常耗时,需要尽可能减少disk IO次数,提升每次IO读取数据的有效性


B tree中间节点+叶⼦节点都存储index+data
在Search的过程中,只需要将index加载到memory,并不需要data;
B+ tree中间节点只存储index,叶⼦节点存储index+data(可以是data本⾝,也可以是data physical
address)
B+ tree 叶⼦结点通过linked list的形式连接在⼀起,非常适合range search
B+ tree冗余节点(非叶⼦节点)的存在,使得insert/delete效率比B tree⾼效

mysql innodb使⽤B+ Tree index

mysql为什么使⽤B+ Tree?

MySQL 的数据是持久化的,意味着数据(index+data)是保存到磁盘上
sector 512byte, 1 block = 8 sector = 4kb; linux下disk IO每次读取⼀个block的数据,即4kb
mysql在search的过程中,需要多次disk IO; 并且⽀持range search;
disk IO非常耗时,需要尽可能减少disk IO次数,提升每次IO读取数据的有效性

13 / 52
notes-图解mysql.md 4/29/2022

⼀个node对应1个page, innodb_page_size = 16KB (linux page_size=4kb)


mysql⼀次性disk IO读取16kb,也就是⼀次性读取4 disk block = 4*4kb
中间节点-Index Page; 叶⼦节点-Leaf Page;
leaf page的单向linkedlist变成了double linkedlist,⽅便左右遍历
index page增加了double linkedlist
page之间通过double linkedlist的形式组织起来,物理上不连续,但是逻辑上连续
page内部的user records之间先分组(group/slot), group/slot内部records通过single linkedlist连接起来,
page内部可以使⽤binary search加快速度
维护index索引需要代价,insert/delete记录的过程中需要node/page split,需要尽可能减少node/page
split次数

>show variables like 'innodb_page_size';


+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| innodb_page_size | 16384 |

14 / 52
notes-图解mysql.md 4/29/2022

+------------------+-------+
1 row in set (0.00 sec)

clustered index vs secondary index:

clustered index只有1个, 存储primary key+data (id,age,name)


secondary index可以有N个, 存储secondary index+primary key (name,id)

回表(查找2个B+ Tree) vs 索引覆盖(covering index,查找1个B+ Tree)

R-tree(Rectangle tree)
⽤于空间数据index
R means rectangle

Trie
存储string prefix

MySQL性能优化
todo暂时不完整

mysql性能优化 high performance mysql

mysql查询执⾏过程?

15 / 52
notes-图解mysql.md 4/29/2022

查询优化⼯作实际上就是遵循⼀些原则让MySQL的优化器能够按照预想的合理⽅式运⾏⽽已。

16 / 52
notes-图解mysql.md 4/29/2022

Steps

1. The client sends the SQL statement to the server.


2. The server checks the query cache. If there’s a hit, it returns the stored result from the cache; otherwise,
it passes the SQL statement to the next step.
3. The server parses, preprocesses, and optimizes the SQL into a query execution plan.
4. The query execution engine executes the plan by making calls to the storage engine API.
5. The server sends the result to the client.

Sending request -> Query cache -> Parser -> Preprocessor->Optimizer->Query


Execution Engine->Returning results client和 mysql connector之间是通过TCP建立连接

Cache:

SQL中可以通过SQL_CACHE和SQL_NO_CACHE来控制某个查询语句是否需要进⾏缓存
可以将query_cache_type设置为DEMAND,这时只有加入SQL_CACHE的查询才会⾛缓存,其他查询则不

Optimizer选择cost最⼩的query execution plan

mysql> SELECT SQL_NO_CACHE COUNT(*) FROM sakila.film_actor;


+----------+
| count(*) |
+----------+
| 5462 |
+----------+
mysql> SHOW STATUS LIKE 'last_query_cost';
+-----------------+-------------+
| Variable_name | Value |
+-----------------+-------------+

17 / 52
notes-图解mysql.md 4/29/2022

| Last_query_cost | 1040.599000 |
+-----------------+-------------+
# we need 1,040 random data page reads to execute the query

mysql性能优化tips
Scheme设计与数据类型优化 创建⾼性能索引(clustered index,secondary index, union index,unique index)

selectivity

索引的顺序对于查询是⾄关重要的,很明显应该把选择性更⾼的字段放到索引的前⾯,这样通过第⼀个
字段就可以过滤掉⼤多数不符合条件的数据。
primary key/unique index的选择性是1,这是最好的索引选择性,性能也是最好的。

SELECT * FROM payment where staff_id = 2 and customer_id = 584;

select count(distinct staff_id)/count(*) as staff_id_selectivity,


count(distinct customer_id)/count(*) as customer_id_selectivity,
count(*) from payment

Transaction
以转账transfer为例

transaction特性
ACID(Atomicity、Consistency、Isolation、Durability,即原⼦性、⼀致性、隔离性、持久性)

Atomicity: redo log


Consistency: undo log
Isolation: MVCC lock来保证
Durability: redo log

mysql innodb⽀持事务(table lock, row lock),myisam则不⽀持(不⽀持row lock)

If your database (mysql innodb) is transactional, then there simply no way to execute sqls "outside of a
transaction".
mysql innodb引擎是⽀持transaction的, 所有执⾏的sqls语句(select/insert/update/delete)都是在
transaction中执⾏的;

SQL/MySQL isolation level(标准SQL隔离级别)

18 / 52
notes-图解mysql.md 4/29/2022

Read Uncommitted: RU 读未提交, ⼀个事务还没提交时,它做的变更就能被别的事务看到。


Read Committed: RC 读提交, ⼀个事务提交之后,它做的变更才会被其他事务看到。
Repeatable Read: RR 可重复读, ⼀个事务执⾏过程中看到的数据,总是跟这个事务在启动时看到的数据
时⼀致的。 (mysql innodb默认level)
Serializable: S 序列化, 对于同⼀⾏记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后
访问的事务必须等前⼀个事务执⾏完成,才能继续执⾏。

mysql innodb默认是Repeatable Read隔离级别, SQL标准的RR会存在phantom read问题,但是mysql的


RR通过next-key lock解决了phantom read问题;

多个transaction同时执⾏,可能出现dirty read, non-repeatable read, phantom read

dirty read: 脏读, 读到其他事务未提交的数据;


lost update: ???
non-repeatable read: 不可重复读, 前后读取的数据不⼀致;
phantom read: 幻读, 前后读取的记录数量不⼀致。

Gap lock可以解决幻读(why???) todo

19 / 52
notes-图解mysql.md 4/29/2022

mysql如何设置isolation level?

# SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;

其中的level可选值有4个:
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

mysql> show variables like 'tx_isolation';


+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| tx_isolation | REPEATABLE-READ |
+---------------+-----------------+

mysql 4种isolation level如何实现?


Read Uncommitted: 直接读取最新数据(latest record)
Read Committed: Read View, 每次select都⽣成⼀个新的Read View; (B事务提交之后的变动对未提交的A
事务可⻅)
Repeatable Read: Read View, 第⼀次select时⽣成⼀个Read View, 后续所有的select共⽤这个Read View;
(B事务提交之后的变动对未提交的A事务不可⻅, A事务中读取的数据和事务A开始之前⼀致; 例外情况
是,A事务内部⾃⼰的变动则可以⻅; 比如select - update - select,则前后2次select内容不⼀致,第2次
select可以看到update的变动内容)
Serializable: read/write lock 读写锁实现

MVCC

MVCC: multi-version concurrency control,多版本并发控制

对于使⽤InnoDB存储引擎的表来说,它的clustered index记录中都包含必要的隐藏列

trx_id: 事务修改了clustered index记录时,会存储对应的事务id

20 / 52
notes-图解mysql.md 4/29/2022

roll_pointer: 指向每⼀个旧版本记录(undo log)

Read View如何⼯作?

ReadView所解决的问题是使⽤Read Committed和Repetable Read isolation level的事务

Read Committed: 每次select都⽣成⼀个新的Read View;


Repeatable Read: 第⼀次select时⽣成⼀个Read View, 后续所有的select共⽤这个Read View;

m_ids:表⽰在⽣成ReadView时当前系统中活跃的(uncommitted)事务id列表。
min_trx_id:表⽰在⽣成ReadView时当前系统中活跃的(uncommitted)事务中最⼩的事务id,也就是m_ids
中的最⼩值。
max_trx_id:表⽰⽣成ReadView时系统中应该分配给下⼀个事务的id值。
creator_trx_id:表⽰⽣成该ReadView的事务的事务id。

Read View如何⼯作? 有了Read View,访问user record的时候,只需要按照如下规则就可以判断user record的


version是否对当前事务可⻅(visible): 0. trx_id == creator_trx_id:record对应的事务就是当前的事务,可以访
问;

1. trx_id < min_trx_id:record对应的事务已经committed,可以访问;


2. trx_id >= max_trx_id:record对应的事务还没有开启,不可以访问;
3. min_trx_id<= trx_id < max_trx_id

(1) trx_id在m_ids, recore对应的事务uncommitted, 不可以访问


(2) trx_id不在m_ids, record对应的事务committed, 可以访问

如果可以访问,则返回该record; 如果不可以访问,则沿着roll_pointer寻找历史version,然后进⾏同样的
判断;直到最后⼀个version.

transaction 4种isolation level example

21 / 52
notes-图解mysql.md 4/29/2022

read uncommitted: v1 = 200, v2 = 200, v3 = 200


read committed: v1 = 100, v2 = 200, v3 = 200
repeatable read: v1 = 100, v2 = 100, v3 = 200
serializable: v1 = 100, v2 = 100, v3 = 200(发⽣了读写冲突,A事务committed之后,B事务才能继续执⾏)

Repeatable Read例⼦

22 / 52
notes-图解mysql.md 4/29/2022

23 / 52
notes-图解mysql.md 4/29/2022

24 / 52
notes-图解mysql.md 4/29/2022

0. A第⼀次select的时候会创建1个Read View, m_ids=[51,52],后续所有select都会使⽤;

1. A读取record,trx_id=50 < min_trx_id; 可以读取,结果为100;


2. B读取record,trx_id=50 < min_trx_id; 可以读取,结果为100;
3. B更新100为200, 会增加⼀条record,trx_id=52; 并通过roll_pointer以linkedlist的形式连接起来,形成版本
链;
4. A读取record,trx_id=52; trx_id在m_ids中间,表明record对应的事务还没有提交,不可以读取;沿着版本
链,找到上⼀个版本, trx_id = 50 < min_trx_id, 可以读取,结果为V1=100;
5. B提交之后,A再次读取record,trx_id=52和步骤4类似,结果为V1=100;
6. A提交;

B提交之前V1=100, 提交之后V1=100

Session A Session B

SET autocommit=0; SET autocommit=0;


time
| SELECT * FROM t;
| empty set
| INSERT INTO t VALUES (1, 2);
|
v SELECT * FROM t;
empty set
COMMIT;

SELECT * FROM t;
empty set

COMMIT;

SELECT * FROM t;

25 / 52
notes-图解mysql.md 4/29/2022

---------------------
| 1 | 2 |
---------------------

create table t(id int, a int) engine=innodb;


insert into t values(1,100);

# Repeatable Read
#Session 1
begin;
select * from t where id=1; # 1,100
update t set a = 200 where id=1;
select * from t where id=1; # 1,200

#Session 2
delete from t where id=1; #implicit autocommit

#Session 1
select * from table; # 1,200
commit;

# Session 1
select * from table; # null

Read Committed例⼦

26 / 52
notes-图解mysql.md 4/29/2022

27 / 52
notes-图解mysql.md 4/29/2022

0. A第⼀次select的时候会创建1个Read View, m_ids=[51,52];

1. A读取record,trx_id=50 < min_trx_id; 可以读取,结果为100;


2. B读取record,trx_id=50 < min_trx_id; 可以读取,结果为100;
3. B更新100为200, 会增加⼀条record,trx_id=52; 并通过roll_pointer以linkedlist的形式连接起来,形成版本
链;
4. A读取record,trx_id=52; 在此select之前,会再此⽣成⼀个Read View, m_ids = [51,52]; trx_id在m_ids中
间,表明record对应的事务还没有提交,不可以读取;沿着版本链,找到上⼀个版本, trx_id = 50 <
min_trx_id, 可以读取,结果为V1=100;
5. B提交之后,A再次读取record,trx_id=52; 在此select之前,会再此⽣成⼀个Read View, m_ids = [51]; B
提交之后,事务A此处⽣成的Read View的m_ids中已经没有52了; trx_id不在m_ids中,表明record对应
的事务已经committed,可以读取,结果为V1=200;

B提交之前V1=100, 提交之后V1=200

Lock
28 / 52
notes-图解mysql.md 4/29/2022

mysql innodb lock

Shared Lock vs Exclusive Lock


最基础的 共享锁(read lock)vs 排他锁(write lock)

shared lock: S
exclusive lock: X

SS兼容,其他SX,XS,XX都不兼容

mysql 3 type locks


global locks: global read lock(FTWRL)
table locks: regular table lock, meta data lock(MDL), intension lock(IS,IX), AUTO-INC lock,
row locks: record lock, gap lock, next-key lock;

RC仅⽀持record lock(1种row lock), RR⽀持还⽀持gap lock(3种row locks), RR通过next-key lock解决了幻


读问题(phantom read)

mysql autocommit
MySQL's storage engine is from MyISAM to InnoDB, and locks are from table locks to row locks.

MyISAM⽀持global lock, table lock,不⽀持row lock,⽆法⽀持transaction


InnoDB⽀持global lock, table lock, row lock,通过row lock来提供对transaction的⽀持

RC仅⽀持record lock(1种row lock), RR⽀持还⽀持gap lock(3种row locks) row lock可能导致deadlock,


concurrency性更好 mysql innodb引擎是⽀持transaction的, 所有执⾏的sqls语句
(select/insert/update/delete)都是在transaction中执⾏的;

innodb默认情况下autocommit = 1/ON

# 如何查看autocommit?
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)

autocommit规则:

默认autocommit = 1; 所有的select/insert/update/delete语句都在⼀个独立的transaction中执⾏并⾃动提
交;
如何disable autocommit Disable autocommit by doing one of the following:

1. autocommit=1, 通过start transaction;/begin;显⽰启动事务,然后可以执⾏多条sql语句,直到⼿动


commit/rollback;

29 / 52
notes-图解mysql.md 4/29/2022

2. set autocommit = 0; 系统⾃动开启了事务,但是需要⼿动commit;

create table t(id int, a int) engine=innodb;

set autocommit=1;
# transaction 1: 每个sql都会隐式创建事务并⾃动提交
select * from t where id=1;

# transaction 2: 每个sql都会隐式创建事务并⾃动提交
select * from t where id=2;

# transaction 3
begin; # ⼿动开启事务
select * from t where id=1;
insert into t values (2),(3);
commit; # ⼿动提交

# transaction 4
set autocommit=0; # ⾃动开启事务
select * from t where id=1;
insert into t values (4),(5);
commit; # ⼿动提交

global lock
global read lock(FTWRL)主要⽤于backup database
如果不开启gloal read lock, 可以通过mysqldump --single-transaction开启事务来确保数据⼀致性,

--single-transaction只⽀持mysql innodb(RR level), 不⽀持mysql myisam, myisam只能通过global


read lock来备份数据库;

#flush tables with read lock;


整个数据库处于read only状态; 其他session⽆法write

# unlock tables;
释放gloal read lock

执⾏后,整个数据库就处于readonly状态了,这时其他线程执⾏以下write操作,都会被阻塞; 当前线程执⾏以下
操作non block,但会失败failed;

DML, 对数据的增删查改操作,比如 insert、delete、update等语句(select需要加read lock,可以执⾏,不被


blocked);

30 / 52
notes-图解mysql.md 4/29/2022

DDL, 对表结构的更改操作,比如 alter table、drop table 等语句。 来看⼀个例⼦

1. A中加global read lock;


2. A select成功,insert失败;
3. B select成功; insert被blocked;
4. A中unlock tables释放global read lock; B的insert成功执⾏;

table locks
connection/session/thread是对应的概念

mysql> select connection_id();


+-----------------+
| connection_id() |
+-----------------+
| 90 |
+-----------------+
1 row in set (0.00 sec)

mysql> show processlist;


+----+------+-----------+------+---------+------+----------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+------+---------+------+----------+------------------+
| 90 | root | localhost | test | Query | 0 | starting | show processlist |
| 92 | root | localhost | test | Sleep | 59 | | NULL |
| 93 | root | localhost | test | Sleep | 766 | | NULL |
+----+------+-----------+------+---------+------+----------+------------------+
3 rows in set (0.00 sec)

regular table lock(S,X)

#lock tables t_student read; // S


...
#unlock tables;

#lock tables t_stuent wirte; // X


...

31 / 52
notes-图解mysql.md 4/29/2022

#unlock tables;
show open tables where in_use >=1;

例⼦1:

1. A read lock, select成功,insert失败;


2. B read lock, select成功,insert失败;
3. B释放read lock, insert blocked...
4. A释放read lock, B的insert成功;

例⼦2:

1. A read lock, select成功,insert失败;


2. B write lock 被blocked...
3. C select被blocked...
4. A释放read lock, B write lock成功; C select任然blocked...
5. A write lock 被blocked...
6. B释放write lock, A write lock成功;
7. A释放write lock, C select 成功;

B申请了table write lock, 后续所有C,D...进⾏的select都会被blocked... (write lock申请的优先级>read lock)

32 / 52
notes-图解mysql.md 4/29/2022

例⼦3:

1. A lock table t;
2. A select t可以成功;
3. A select t2失败;

33 / 52
notes-图解mysql.md 4/29/2022

例⼦4(同时lock t和t2解决3的问题):

例⼦5: 如何释放table locks

unlock tables;
lock tables t read 之后⼜来了⼀个lock tables t2 read or lock tables t write会将session
之前的所有lock释放;

34 / 52
notes-图解mysql.md 4/29/2022

table lock是在unlock tables or lock tables t2 read之后释放掉

meta data lock(MDL S,X)

我们不需要显⽰的使⽤ MDL,因为当使⽤transaction对数据库表进⾏操作时,会⾃动给这个表加上 MDL:

对⼀张表进⾏ CRUD(select,insert,update,delete) 操作时,加的是 MDL read lock;


change table structure,加的是 MDL write lock;

MDL是隐式的,会在transaction commit之后释放;

35 / 52
notes-图解mysql.md 4/29/2022

例⼦1: 同⼀个session的T1中先select,然后alter会释放之前的MDL read lock;

1. begin开启事务
2. insert 获取MDL read lock;
3. alter释放之前的read lock, 重新获取MDL write lock;
4. select释放之前的write lock, 重新获取MDL read lock;
5. commit 之后释放最后⼀个MDL read lock;

例⼦2:如果T1是⼀个⻓事务(uncommitted)执⾏了select未提交,T2进⾏select, T3中对table进⾏alter, T4进⾏


select, 会发⽣什么情况?

36 / 52
notes-图解mysql.md 4/29/2022

1. T1 select会获取MDL read lock;(⼀直没有release)


2. T2 select可以正常获取MDL read lock;
3. T3 alter需要申请MDL write lock,和T1冲突, 导致T3 blocked...
4. T4 select申请MDL read lock, 由于T3,T4也被blocked...

为什么线程 T3 因为申请不到 MDL 写锁,⽽导致后续的申请读锁的查询操作也会被阻塞?

这是因为申请 MDL 锁的操作会形成⼀个queue,队列中 write lock请求的优先级> read lock请求的


优先级,⼀旦出现 MDL write lock等待,会阻塞后续该表的所有 CRUD 操作。

结论: MDL write lock(alter table)⼀旦被阻塞,后续所有的MDL read lock(select,insert,update,delete)等


操作都会被阻塞

如何查看blocked MDL lock?

intention Lock(IS,IX)

The main purpose of intention locks is to show that someone is locking a row, or going to lock a row in the
table. 意向锁的⽬的是为了快速判断表⾥是否有记录被加锁。 IS/IX表明table的某⼀⾏被lock了,那么在申请
table lock的时候,就可以快速判断是否有冲突,⽆需遍历所有的⾏进⾏判断

intention shared lock: table IS -> row S


intention exclusive lock: table IX -> row X

table S/S兼容, S/X, X/S不兼容;

意向锁全部兼容; IS/IS,IS/IX,IX/IX互相兼容;

row Record: S/S兼容, S/X, X/S不兼容; row Gap: S/X兼容


37 / 52
notes-图解mysql.md 4/29/2022

IX,IS是表级锁,不会和row的X,S锁发⽣冲突。只会和table的X,S发⽣冲突(lock tables ...

read/write;)

规则:

row可读,则可以申请table read; (IS, S兼容)


row可读,则不能申请table write; (IS, X冲突)
row可写,则不能申请table read/write; (IX, S/X冲突)

table的S/X申请,只需要和table IS/IX判断即可,不需要和table的每⼀⾏S/X进⾏判断, 时间复杂度从


O(n)->O(1); 将1个table lock和N个row lock之间的判断简化为 1 table lock(S,X) vs 1 intension lock(IS,IX)

看⼀个例⼦:

1. table共有n⾏记录,事务A锁住了其中1⾏,只能read; (row S);


2. 事务B现在要申请table write lock (table X), 如何判断冲突?

判断Steps:

step1: 判断其他事务A是否有table lock? (简单,直接判断)


step2: 判断table的所有row是否有row lock? (需要遍历,时间复杂度O(N))

有没有快速⽅法?-> IS,IX

申请row S,增加table IS; 申请row X,增加table IX; 现在step2: 只需要判断IS/IX; 现在发现IS存在,和B申请


的table X冲突,则blocked. 时间复杂度从O(n)->O(1);

如何使⽤sql增加IS/IX?

select ... # 普通select没有任何row lock


#IS(row S):select ... lock in share mode ;
#IX(row X):select ... for update;

AUTO-INC lock

insert row带有auto_increment

InnoDB 存储引擎提供了个 innodb_autoinc_lock_mode 的系统变量,是⽤来控制选择⽤ AUTO-INC 锁,还是


轻量级的锁。

当 innodb_autoinc_lock_mode = 0,就采⽤ AUTO-INC 锁;


当 innodb_autoinc_lock_mode = 2,就采⽤轻量级锁;
当 innodb_autoinc_lock_mode = 1(default),这个是默认值,两种锁混着⽤,如果能够确定插入记录的
数量就采⽤轻量级锁,不确定时就采⽤ AUTO-INC 锁。

mode = 0, insert完毕就释放,⽆需等待commit;

38 / 52
notes-图解mysql.md 4/29/2022

row lock
row lock分类:

gap lock: (a,b) ⽤来锁住Index Record之间的gap,包括gap S/X (gap兼容)


record lock: [b] ⽤来锁住Index Record,包括record S/X
next-key lock = gap lock + record lock
Insert Intention Locks: 插入意向锁(IIL, 只有insert语句才会有,是⼀种特殊的gap lock)

注意: gap S和gap X是兼容的 !!! IIL和gap lock是不兼容的(⽤来解决inserted row幻读问题);

RR⽀持Next-Key Lock、Gap Lock和Record Lock,RC仅⽀持Record Lock

Insert Intension lock

Insert Intention Locks: 插入意向锁

插入意向锁IIL是⼀种特殊的gap lock
在insert时,使⽤IIL代替常规的gap lock;
IIL age (10,20) age = 15; IIL age (10,20) age = 16; IIL 的gap lock区间相同,只要index不同,则T1和T2事务
之间就不会冲突等待;
解决了并发插入的问题

IIL和gap lock是冲突的,T1有了gap lock, T2的insert如果申请IIL则blocked, T3的insert如果申请IIL则


blocked...;

T1有了gap lock, T2,T3则不能insert; T1释放了gap lock之后,T2和T3则可以insert;

来看⼀个例⼦:

# MySql,InnoDB,Repeatable-Read:users(id PK, name, age KEY)

id name age
1 Mike 10
2 Jone 20
39 / 52
notes-图解mysql.md 4/29/2022

3 Tony 30

# T1
begin;
INSERT INTO users SELECT 4, 'Bill', 15;

# T2
begin;
INSERT INTO users SELECT 5, 'Louis', 16; # OK

#================================================
# case 1: 如果insert使⽤常规gap lock会如何?
#================================================
# t1事务:
table IX;
id-> record lock [4]
age-> gap lock(10,20) age = 15

# t2事务
table IX; (OK)
id-> record lock [5] (???)
age-> gap lock(10,20) age = 16 (T1已经lock了(10,20)区间,T2⽆法获取lock,所以
blocked...)

#================================================
# case 2: insert使⽤IIL替代常规gap lock
#================================================
# t1事务:
table IX;
id-> record lock [4]
age-> IIL gap lock(10,20) age = 15

# t2事务
table IX; (OK)
id-> record lock [5] (OK)
age-> IIL gap lock(10,20) age = 16 (OK, 2个IIL不冲突)

例⼦2

mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);

# T1
mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+-----+
| id |
+-----+
| 102 |
+-----+
# T1加了gap lock (100,102]表明此区间不允许insert

40 / 52
notes-图解mysql.md 4/29/2022

# T2
mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);
# T2需要申请IIL gap lock (90,102), 发现T1的gap lock没有释放,所以blocked...

sql语句加锁

普通select 不会加row lock的,是利⽤ MVCC 实现⼀致性读,是⽆锁的。


select ... for update加IX,row X; select ... lock in share mode 加IS,row S;
insert/update/delete 加IX,row X;

mysql 加锁的基本单位是next-key lock, (a,b], 并且只有访问到的index才会加锁

在进⾏range search时,会加next-key lock


在进⾏equal search时:
如果是⾛主键或者唯⼀索引,next-key lock退化为record lock;
如果是是⾛普通索引,next-key lock退化为gap lock;

唯⼀索引等值查询:

当查询的记录是存在的,next-key lock 会退化成「记录锁」。


当查询的记录是不存在的,next-key lock 会退化成「间隙锁」。

非唯⼀索引等值查询:

当查询的记录存在时,除了会加 next-key lock 外,还额外加间隙锁,也就是会加两把锁。


当查询的记录不存在时,只会加 next-key lock,然后会退化为间隙锁,也就是只会加⼀把锁。

对应的查询结果example:

X,REC_NOT_GAP 15 那条数据的⾏锁 [15]


X,GAP 15 数据之前的间隙,不包含 15 (10,15)
X 15 数据的间隙,包含 15, (10,15]

(1) unique key+ equal search + exist

# 开启metadata_locks (查询不到数据和mysql 版本有关系)


UPDATE performance_schema.setup_instruments
SET ENABLED = 'YES', TIMED = 'YES'
WHERE NAME = 'wait/lock/metadata/sql/mdl';
select * from performance_schema.metadata_locks\G

create table t(id int primary key, a int) engine = innodb;


insert into t values(0,0), (5,5), (10, 10), (15,15), (20,20);

# T1
begin; select * from t where id = 10 for update;

# IX, next-key lock (5,10] id=10存在,退化为record lock[10]


LOCK_TYPE, LOCK_MODE, LOCK_DATA, INDEX_NAME
table, IX, null null

41 / 52
notes-图解mysql.md 4/29/2022

record, X,REC_NOT_GAP, 10, PRIMARY


(IX + X record lock)

其他事务更新id=10 blocked...
# t2
update t set a = 200 where id = 10; # blocked

#====================================================
#====================================================
# T2
begin; select * from t where id = 10 lock in share mode;

# IX, next-key lock (5,10] id=10存在,退化为record lock[10]


LOCK_TYPE, LOCK_MODE, LOCK_DATA, INDEX_NAME
table, IS, null , null
record, S,REC_NOT_GAP, 10, PRIMARY

(2) unique key+ equal search + not exist

# t1
begin; select * from t where id = 11 for update;
# IX, next-key lock (11,15] id=11不存在,退化为gap lock(11,15)
LOCK_TYPE, LOCK_MODE, LOCK_DATA, INDEX_NAME
record, X,GAP 15 PRIMARY

# t2
update t set a = 100 where id = 10; # OK
update t set a = 150 where id = 15; # OK
insert into t values(11,11); # blocked...

(3) unique key+ range search + not exist

begin; select * from t where id >= 10 and id < 11 for update;


# 10 -> (5,10] id=10满⾜range search---> [10]
# 11 -> (10,15] id=15不满⾜range search---> (10,15)
最终[10,15)被锁住

update t set a = 100 where id = 10; # blocked


insert into t values(13,13); # blocked
update t set a = 1500 where id = 15; # OK

begin; select * from t where id > 10 and id <= 15 for update;


# (10,15]

update t set a = 100 where id = 10; # ok


insert into t values(13,13); # blocked
update t set a = 150 where id = 15; # blocked
insert into t values(16,16); # ok

42 / 52
notes-图解mysql.md 4/29/2022

sql_safe_updates
默认是0, 如果这个系统变量设置为1的话,意味着update与delete将会受到限制
if set = 0; 在 update 语句的 where 条件没有使⽤索引,就会全表扫描,于是就会对所有记录加上 next-
key 锁(记录锁 + 间隙锁),相当于把整个表锁住了。 会block其他的操作;

see mysql sql_safe_updates

当 sql_safe_updates 设置为 1 时 update 语句必须满⾜如下条件之⼀才能执⾏成功:

1. where + no limit,where条件中必须有索引列;
2. no where + limit;
3. where + limit,where 条件中可以没有索引列;

delete 语句必须满⾜如下条件之⼀才能执⾏成功:

1. where + no limit,where条件中必须有索引列;
2. where + limit,where 条件中可以没有索引列;

update/delete sql_safe_updates总结

UPDATE: where KEY/ limit / where(KEY,NOKEY,CONSTANT) + limit


DELETE: where KEY/ where(KEY,NOKEY) + limit

show global variables like 'sql_safe_updates';


show session variables like 'sql_safe_updates';

select @@global.sql_safe_updates;
select @@session.sql_safe_updates;

# sql_safe_updates默认=0
mysql8> show variables like "sql_safe_updates";
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| sql_safe_updates | OFF |
+------------------+-------+
1 row in set (0.01 sec)

mysql> set sql_safe_updates = 1;

43 / 52
notes-图解mysql.md 4/29/2022

Query OK, 0 rows affected (0.00 sec)

mysql> show variables like 'sql_safe_updates';


+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| sql_safe_updates | ON |
+------------------+-------+
1 row in set (0.01 sec)

幻读 (phantom read)
幻读仅专指“新插入的⾏” new inserted row

mysql innodb, MVCC, RR级别:

select: snapshot read(快照读,前后多次select使⽤同⼀个Read View判断)


insert/update/delete: current read(当前读,读取最新version)
select ... for update(current read当前读)

RR级别select(snapshot read)没有phantom read现象; RR级别select...for update(current read)存在


phantom read现象,mysql innodb通过 next-key lock解决幻读问题

RR级别下:

1. 普通select是 snapshot read,看不到session B的insert data; 没有phantom read问题;


2. select ...for update是 current read,可以看到B insert data; 存在phantom read问题; 2.1 假设没有next-key
lock, 则select ...for update可以读到B的insert data,出现幻读问题; 2.2(***) 实际是有next-key lock, 则select
...for update 可以锁住(2,+pos)区间,session B的insert data操作被blocked...; 这样就解决了phantom
read问题;

RR级别select...for update(current read)存在phantom read现象,mysql innodb通过 next-key lock解决幻


读问题

44 / 52
notes-图解mysql.md 4/29/2022

图解3步:

45 / 52
notes-图解mysql.md 4/29/2022

46 / 52
notes-图解mysql.md 4/29/2022

mysql常⽤sql

#show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
+--------------------+

#select connection_id();
+-----------------+
| connection_id() |
+-----------------+
| 90 |
+-----------------+

#select now();
+---------------------+
| now() |
+---------------------+
| 2020-02-11 08:59:44 |
+---------------------+

#select database();
+------------+
| database() |
+------------+
| test |
+------------+

#show full processlist;


# 可以查看Table Meta Data locks (MDL read/write lock)
+----+------+-----------+------+---------+------+----------+----------------------
-+
| Id | User | Host | db | Command | Time | State | Info
|
+----+------+-----------+------+---------+------+----------+----------------------
-+
| 90 | root | localhost | test | Query | 0 | starting | show full processlist
|
+----+------+-----------+------+---------+------+----------+----------------------
-+

#show open tables where in_use >=1;

create table t (id int,a int) engine=innodb;

47 / 52
notes-图解mysql.md 4/29/2022

insert into t values(1,100),(2,200);


select * from t;
select * from t where id=1;
update t set a=200 where id=1;
alter table t add (b int);
drop table t;

deadlock
deadlock的4个必要条件

1. mutual exclusive: 互斥
2. hold and wait: 占有且等待
3. no preemption: 不可强占⽤
4. circular wait: 循环等待

只要系统发⽣死锁,这些条件必然成立,但是只要破坏任意⼀个条件就死锁就不会成立。

有两种策略通过「打破循环等待条件」来解除死锁状态:

设置innodb_lock_wait_timeout = 50s; 超过时间,T1会rollback 释放lock; T2就可以执⾏了;


开启deadlock detection (innodb_deadlock_detect=ON), ⼀旦检测到deadlock,会将deadlock中的第⼀个
tranaction进⾏rollback;

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction;

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

48 / 52
notes-图解mysql.md 4/29/2022

select ... for update deadlock

select.. for update会导致deadlock

master-slave replication(主从复制)
mysql replication 好处:

读写分离
HA ⾼可⽤
⽅便故障切换
架构扩展

49 / 52
notes-图解mysql.md 4/29/2022

mysql master-slave形式
⼀主⼀从(M-S)
⼀主多从(M-SSS)
多主⼀从(MMM-S)
双主复制(M-M)
级联复制(M-S-SSS)

级联复制解决了⼀主多从场景下多个从库复制对主库的压⼒,带来的弊端就是数据同步延迟比较⼤

mysql主从复制原理
3 threads:

master: log dump thread


slave: io thread + sql thread per db

binlog + relay log

master会⽣成⼀个 log dump thread,⽤来给从库 I/O 线程传 Binlog 数据。


slave的 I/O thread接收 Binlog,并将得到的 Binlog 写到本地的 relay log (中继⽇志)文件中。
salve的SQL thread,会读取 relay log,并解析成 SQL 语句逐⼀执⾏。

MySQL 主从复制默认是 async异步的模式。MySQL增删改操作会全部记录在 Binlog 中,当 slave 节点连


接 master 时,会主动从 master 处获取最新的 Binlog 文件。并把 Binlog 存储到本地的 relay log 中,然
后去执⾏ relay log 的更新内容。

binlog format

MySQL 主从复制有三种⽅式:

基于SQL语句的复制(statement-based replication,SBR): 只记录sql, binlog较⼩,节约io, 会导致slave


数据不⼀致(now(),...)
基于⾏的复制(row-based replication,RBR): 只记录data, binlog比较⼤,slave同步时间⻓;

50 / 52
notes-图解mysql.md 4/29/2022

混合模式复制(mixed-based replication,MBR)

对应的binlog文件的格式也有三种:STATEMENT,ROW,MIXED。

mysql binlog主从复制
async 异步: commit之后立⻢return给user; 不需要等待slave节点是否已经同步完成; (默认mode, 性能好,
但是slave可能数据不完整)
semi-sync 半同步: commit之后,⾄少同步binlog给⼀个slave节点并保存为relay-log就可以return给user;
(介于2者之间)
full-sync 全同步: commit之后,同步binlog给所有的slave并执⾏sql成功之后return给user; (性能最差,
slave数据完整)

mysql>change master to
master_host='192.168.199.117',
master_user='slave',

51 / 52
notes-图解mysql.md 4/29/2022

master_port=7000,
master_password='slavepass',
master_log_file='mysql-bin.000008',
master_log_pos=0;

mysql>start slave;
mysql>show slave status\G;

mysql新⼀代主从复制-GTID
GTID = server_uuid:transaction_id

vim my.cfg
#GTID:
gtid_mode = on
read_only = on

# slave
mysql> change master to
master_host='172.23.3.66',master_user='slave1',master_password='123456',master_aut
o_position=1;
Query OK, 0 rows affected, 2 warnings (0.26 sec)

mysql> start slave;

(gtid_mode=on) MASTER_AUTO_POSITION = 1, MASTER_LOG_FILE,MASTER_LOG_POS不再使⽤;


(gtid_mode=off) MASTER_AUTO_POSITION = 0, 需要⼿动指定MASTER_LOG_FILE,MASTER_LOG_POS;

binlog based position vs gtid auto position

mysql 1 master + 2 slave example


1 master + 2 slave实战配置

Ref
1. mysql doc
2. mysql explain
3. mysql explain
4. mysql MVCC
5. mysql innodb next-key lock

52 / 52

You might also like