0% found this document useful (0 votes)
20 views

notes 图解mysql

notes-图解mysql

Uploaded by

zunlin ke
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
20 views

notes 图解mysql

notes-图解mysql

Uploaded by

zunlin ke
Copyright
© © All Rights Reserved
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