MySQL

BigInt(20) 和 Int(20) 辨别

括号里的数字只用于填充0(ZEROFILL),而跟能存储数字的大小没有区别,所以基本没用,因为一般不会打开ZEROFILL选项。

BigInt存储8字节有符号整数,Int存储4字节有符号整数。

https://stackoverflow.com/questions/3135804/types-in-mysql-bigint20-vs-int20

字符串数据类型

MySQL数据类型
含义

char(n)

固定长度,最多2^8−1个字符,2^8−1个字节

varchar(n)

可变长度,最多2^16−1个字符,2^16−1个字节

tinytext

可变长度,最多2^8−1个字符,2^8−1个字节

text

可变长度,最多2^16−1个字符,2^16−1个字节

mediumtext

可变长度,最多2^24−1个字符,2^24−1个字节

longtext

可变长度,最多2^32−1个字符,2^32−1个字节

varchar和char区别

  • varchar是可变长度,每个值只占用刚好够用的字节再加上一个用来记录其长度的字节;

  • char是固定长度,长度不足的话会在它的右边用空格字符补足。

SQL优化

limit 当 offset 很大时性能低下,改用子查询的分页方式或者JOIN分页方式(使用字段带有索引)

原因:子查询是在索引上完成的,而普通的查询时在数据文件上完成的,通常来说,索引文件要比数据文件小得多,所以操作起来也会更有效率。

使用limit语法分页,会将offset+limit数量的结果集全部查出来,然后取limit条数据。

-- 传统limit,文件扫描
[SQL]SELECT * FROM tableName ORDER BY id LIMIT 500000,2;
受影响的行: 0
时间: 5.371s

-- 子查询方式,索引扫描
[SQL]
SELECT * FROM tableName
WHERE id >= (SELECT id FROM tableName ORDER BY id LIMIT 500000 , 1)
LIMIT 2;
受影响的行: 0
时间: 0.274s

-- JOIN分页方式
[SQL]
SELECT *
FROM tableName AS t1
JOIN (SELECT id FROM tableName ORDER BY id desc LIMIT 500000, 1) AS t2
WHERE t1.id <= t2.id ORDER BY t1.id desc LIMIT 2;
受影响的行: 0
时间: 0.278s

避免join、子查询

update尽量少量更新,避免造成主从延迟

避免NULL,难以优化

索引列不允许为null

单列索引不存null值,复合索引不存全为null的值,如果列允许为 null,可能会得到不符合预期的结果集。

数据区分度不高字段不宜建索引

如性别、状态等字段,不宜建索引,维护索引需要开销,查找时也不能有效过滤数据。

区分度可以使用 count(distinct(列名))/count(*) 来计算,在80%以上的时候就可以建立索引。

使用覆盖索引

所谓覆盖索引,是指被查询的列,数据能从索引中取得,而不用通过行定位符再到数据表上获取,能够极大的提高性能。

负向条件查询不能使用索引

负向条件有:!=、<>、not in、not exists、not like 等。

select * from artile where status != 1 and status != 2;
-- 可以使用in进行优化:
select * from artile where status in (0,3)

数据库三范式

第一范式(1st NF -列都是不可再分)

第一范式的目标是确保每列的原子性:如果每列都是不可再分的最小数据单元(也称为最小的原子单元),则满足第一范式(1NF)。

第二范式(2nd NF-每个表只描述一件事情)

首先满足第一范式,并且表中非主键列不存在对主键的部分依赖。 第二范式要求每个表只描述一件事情。

第三范式(3rd NF- 不存在对非主键列的传递依赖)

第三范式定义是,满足第二范式,并且表中的列不存在对非主键列的传递依赖。

数据库事务特性

原子性(Atomicity)

事务是一个完整的操作。事务的各步操作是不可分的(原子的);要么都执行,要么都不执行。

一致性(Consistency)

当事务完成时,数据必须处于一致状态。

隔离性(Isolation)

对数据进行修改的所有并发事务是彼此隔离的,这表明事务必须是独立的,它不应以任何方式依赖于或影响其他事务。

永久性(Durability)

事务完成后,它对数据库的修改被永久保持,事务日志能够保持事务的永久性。

Join连接

LEFT JOIN(左连接):获取左表所有记录,即使右表没有对应匹配的记录。 RIGHT JOIN(右连接): 与 LEFT JOIN 相反,用于获取右表所有记录,即使左表没有对应匹配的记录。 INNER JOIN(内连接或等值连接):获取两个表中字段匹配关系的记录,取交集。

MYSQL引擎类型

1.ISAM:ISAM执行读取操作的速度很快,而且不占用大量的内存和存储资源。不支持事务处理,也不能够容错。

2.MYISAM:提供ISAM里所没有的索引和字段管理的大量功能。优化多个并发的读写操作。

3.HEAP:允许只驻留在内存里的临时表格

4.INNODB

5.BERKLEY(BDB)

INNODB和BDB包括了对事务处理和外来键的支持

数据库并发控制

1.悲观锁

如果一个事务执行的操作都某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。在整个数据处理过程中,将数据处于锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

  • 排它锁(写锁):若事务 1 对数据对象A加上X锁,事务 1 可以读A也可以修改A,其他事务不能再对A加任何锁,直到事物 1 释放A上的锁

set autocommit=0; (不自动提交事务)
begin;/begin work;/start transaction; (三者选一就可以)
select status from TABLE where id=1 for update;
insert into TABLE (id,value) values (2,2);
update TABLE set value=2 where id=1;
commit;/commit work;

排它锁会阻塞所有的排它锁和共享锁

  • 共享锁(读锁):其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁

begin;/begin work;/start transaction;  (三者选一就可以)
SELECT * from TABLE where id = 1  lock in share mode;

加上共享锁后,对于update,insert,delete语句会自动加排它锁

优点与不足:悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数。

2.乐观锁

它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。乐观锁相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

优点与不足:乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。

数据库锁

按锁粒度划分

1.行级锁

行锁分为:共享锁排他锁。InnoDB行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁。

# InnoDB共享锁
xxx lock in share mode

# InnoDB排它锁
xxx for update

对于UPDATE、DELETE、INSERT语句,InnoDB会自动给涉及数据集加排他锁(X)。

2.表级锁

表锁:innodb 的行锁是在有索引的情况下,没有索引的表是锁定全表的。

SET AUTOCOMMIT=0;  # 必须设置手动提交事务
LOCAK TABLES t1 WRITE, t2 READ, ...;
[do something with tables t1 and here];
COMMIT;
UNLOCK TABLES;

由于表锁和行锁虽然锁定范围不同,但是会相互冲突。所以当你要加表锁时,势必要先遍历该表的所有记录,判断是否加有排他锁。这种遍历检查的方式显然是一种低效的方式,MySQL 引入了意向锁,来检测表锁和行锁的冲突。

为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁:

  • 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

  • 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

意向锁之间是不会产生冲突的,也不和 AUTO_INC 表锁冲突,它只会阻塞表级读锁或表级写锁,另外,意向锁也不会和行锁冲突,行锁只会和行锁冲突。

3.页级锁

页级锁是 MySQL 中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。

按锁级别划分

共享锁和排它锁

按使用方式划分

悲观锁和乐观锁

数据库隔离级别

1.Read uncommitted 读未提交 不加锁。

2.Read committed 读提交

解决:脏读

Read committed避免脏读的做法其实很简单:就是把释放锁的位置调整到事务提交之后,此时在事务提交前,其他进程是无法对该行数据进行读取的,包括任何操作

3.Repeatable read 重复读 采用MVCC实现可重复读。InnoDB 默认隔离级别。 采用Next-key lock解决幻读现象。

解决:脏读、不可重复读

4.Serializable 序列化

读的时候加共享锁,也就是其他事务可以并发读,但是不能写。写的时候加排它锁,其他事务不能并发写也不能并发读。

解决:脏读、不可重复读、幻读

脏读、不可重复读、幻读

1.读“脏”数据(Dirty Read)

读“脏”数据是指:事务T1修改某一数据,并将其写回磁盘,事务T2读取同一数据后,T1由于某种原因被撤销,这时T1已修改过的数据恢复原值,T2读到的数据就与数据库中的数据不一致,T2读到的数据就为“脏”数据,即不正确的数据。

事务T1读到其他事务如T2未提交的数据。

出现脏读的本质就是因为操作(修改)完该数据就立马释放掉锁,导致读的数据就变成了无用的或者是错误的数据。

2.不可重复读(Non-repeatable Read)

不可重复读,指的是一个事务内根据同一条件对行记录进行多次查询,但是查询出的数据结果不一致,原因就是查询区间数据被其他事务修改了。 不可重复读是指事务T1读取数据后,事务T2执行更新操作,使T1无法再现前一次读取结果。 一个事务在读取某些数据已经发生了改变、或某些记录已经被删除了。

不可重复读包括三种情况:

  • 事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时,得到与前一次不同的值

  • 事务T1按一定条件从数据库中读取了某些数据记录后,事务T2删除了其中部分记录,当T1再次按相同条件读取数据时,发现某些记录消失了

  • 事务T1按一定条件从数据库中读取某些数据记录后,事务T2插入了一些记录,当T1再次按相同条件读取数据时,发现多了一些记录

3.幻读

所谓幻读,指的是在同一事务下,连续执行两次同样的SQL语句可能导致不同的结果,第二次的SQL语句可能会返回之前不存在的行,也就是"幻行"。

一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据。

是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

1、T1:select * from users where id = 1;
2、T2:insert into `users`(`id`, `name`) values (1, 'big cat');
3、T1:insert into `users`(`id`, `name`) values (1, 'big cat');

T1 :主事务,检测表中是否有 id 为 1 的记录,没有则插入,这是我们期望的正常业务逻辑。T2 :干扰事务,目的在于扰乱 T1 的正常的事务执行。在 RR 隔离级别下,1、2 是会正常执行的,3 则会报错主键冲突,对于 T1 的业务来说是执行失败的,这里 T1 就是发生了幻读,因为T1读取的数据状态并不能支持他的下一步的业务,见鬼了一样。

Serializable 隔离级别下 select 操作并会对当前记录加锁,select ... for update。

不可重复读和幻读区别

  • 不可重复读重点在于update和delete,而幻读的重点在于insert

  • 控制角度,不可重复读只需要锁住满足条件的记录,幻读要锁住满足条件及其相近的记录

FAQ: 如何解决幻读问题

  1. MVCC

  2. next-key lock

多版本并发控制(MVCC)

MVCC的实现是基于ReadView版本链以及Undo日志实现的。 通过Undo日志中的版本链ReadView一致性视图来实现的。 MVCC就是在使用READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的SELECT操作时访问记录的版本链的过程,这样可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能;

版本链

对于使用InnoDB存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列

  • DB_TRX_ID:每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务id赋值给DB_TRX_ID隐藏列。

  • DB_ROLL_PTR:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息

ReadView

核心问题就是:需要判断一下版本链中的哪个版本是当前事务可见的。

READVIEW中包含以下几个参数:

  • m_ids:表示在生成READVIEW时当前系统中活跃的读写事务的事务id列表,活跃的是指当前系统中那些尚未提交的事务;

  • min_trx_id:表示在生成READVIEW时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值;

  • max_trx_id:表示生成READVIEW时系统中应该分配给下一个事务的事务id值,由于事务id一般是递增分配的,所以max_trx_id就是m_ids中最大的那个id再加上1;

  • creator_trx_id:表示生成该READVIEW的事务id,由于只有在对表中记录做改动(增删改)时才会为事务分配事务id,所以在一个读取数据的事务中的事务id默认为0;

访问某条记录时,按照如下的规则进行判断版本链中哪个版本对当前读事务是否可见:

  • 版本的trx_id == READVIEW中的creator_trx_id,表示当前读事务正在读取被自己修改过的记录,该版本可以被当前事务访问;

  • 版本trx_id < min_trx_id,表明生成该版本的事务在当前事务生成READVIEW前已经提交了,所以该版本可以被当前事务访问;

  • 版本的trx_id > max_trx_id,表明生成该版本的事务在当前事务生成READVIEW后才开启的,该版本不可被当前事务访问;

  • 版本的trx_id在READVIEW的min_trx_id和max_trx_id之间,那就需要判断一下trx_id属性值是不是在m_ids中。如果在这个范围内,说明创建READVIEW时该事务还处于活跃状态,该版本不可以被当前事务访问;如果不在,说明创建READVIEW时生成该版本的事务已经被提交,该版本可以被当前事务访问;

READ COMMITED和REPEATABLE READ在生成READVIEW时的区别

  • READ COMMITED 在每一次 SELECT 语句前都会生成一个 ReadView,事务期间会更新,因此在其他事务提交前后所得到的 m_ids 列表可能发生变化,使得先前不可见的版本后续又突然可见了。

  • REPEATABLE READ 只在事务的第一个 SELECT 语句时生成一个 ReadView,事务操作期间不更新。

MVCC不能完全解决幻读

# 事务T1,REPEATABLE READ隔离级别下
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM t_test WHERE id = 2;
Empty set (0.01 sec)

# 此时事务T2执行了:INSERT INTO t_test VALUES(2, '哈哈'); 并提交

mysql> UPDATE t_test SET name = '哈哈' WHERE id = 2;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM t_test WHERE id = 2;
+--------+---------+
| id  |  name    |
+--------+---------+
|     2 |   哈哈   |
+--------+---------
1 row in set (0.01 sec)

在REPEATABLE READ隔离级别下,T1第一次执行普通的SELECT语句时生成了一个ReadView,之后T2向表中新插入了一条记录便提交了,ReadView并不能阻止T1执行UPDATE或者DELETE语句来对改动这个新插入的记录(因为T2已经提交,改动该记录并不会造成阻塞),但是这样一来这条新记录的trx_id隐藏列就变成了T1的事务id。 之后T1中再使用普通的SELECT语句去查询这条记录时就可以看到这条记录了,也就把这条记录返回给客户端了。

next-key锁

next-key锁其实包含了记录锁和间隙锁,即锁定一个范围,并且锁定记录本身,InnoDB默认加锁方式是 next-key 锁。

TODO 记录锁(行锁)

间隙锁

当我们用范围条件检索数据而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合范围条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”。InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁。

间隙锁和间隙锁之间是互不冲突的,间隙锁唯一的作用就是为了防止其他事务的插入。

聚簇索引和非聚簇索引

非聚簇索引

索引文件仅保存数据记录的地址。

聚簇索引

表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。因此表数据文件本身就是主索引。

所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。

聚簇索引和非聚簇索引区别:

  • 聚簇索引的叶子节点就是数据节点,而非聚簇索引的叶子节点仍然是索引节点,只不过有指向对应数据块的指针。

  • 聚簇索引的顺序就是数据的物理存储顺序,非聚簇索引的索引顺序与数据物理排列顺序无关。

  • 一个表最多一个聚簇索引,通常默认为主键索引。

  • 聚簇索引是稠密索引(每个搜索键值都有一个索引记录),非聚簇索引是稀疏索引(只为某些值建立索引 )// TODO sure?

联合索引

如表T1有字段a、b、c、d、e,其中a是主键,除e为varchar其余为int类型,并创建了一个联合索引idx_t1_bcd(b,c,d)。

存储结构

联合索引也是一颗B+树,只不过每个节点比单值索引多了几列。存储引擎会首先根据第一个索引列排序,如果第一列相等则再根据第二列排序,以此类推。

查找方式

如查找select * from T1 where b = 12 and c = 14 and d = 3;,存储引擎首先从根节点(一般常驻内存)开始查找,第一个索引的第一个索引列为1,12大于1,第二个索引的第一个索引列为56,12小于56,于是从这俩索引的中间读到下一个节点的磁盘文件地址,从磁盘上Load这个节点,通常伴随一次磁盘IO,然后在内存里去查找。当Load叶子节点的第二个节点时又是一次磁盘IO,比较第一个元素,b=12,c=14,d=3完全符合,于是找到该索引下的data元素即ID值,再从主键索引树上找到最终数据。

最左匹配原则

MySQL 在建立联合索引时会遵循最左前缀匹配的原则,即最左优先,在检索数据时从联合索引的最左边开始匹配。

KEY test_col1_col2_col3 on test(col1,col2,col3);

联合索引 test_col1_col2_col3 实际建立了(col1)、(col1,col2)、(col,col2,col3)三个索引。

SELECT * FROM test WHERE col1=“1” AND clo2=“2” AND clo4=“4”

上面这个查询语句执行时会依照最左前缀匹配原则,检索时会使用索引(col1,col2)进行数据匹配。

ps.

1.索引的字段可以是任意顺序的

SELECT * FROM test WHERE col1=“1” AND clo2=“2”
SELECT * FROM test WHERE col2=“2” AND clo1=“1”

这两个查询语句都会用到索引(col1,col2),mysql创建联合索引的规则是首先会对联合合索引的最左边的,也就是第一个字段col1的数据进行排序,在第一个字段的排序基础上,然后再对后面第二个字段col2进行排序。

2.查询语句SELECT * FROM test WHERE col2=2;也能触发索引

EXPLAIN SELECT * FROM test WHERE col2=2;   // type: index
EXPLAIN SELECT * FROM test WHERE col1=1;   // type: ref

联合索引优点

  • 减少开销。建一个联合索引(col1,col2,col3),实际相当于建了(col1),(col1,col2),(col1,col2,col3)三个索引。每多一个索引,都会增加写操作的开销和磁盘空间的开销。对于大量数据的表,使用联合索引会大大的减少开销!

  • 覆盖索引。对联合索引(col1,col2,col3),如果有如下的SQL: select col1,col2,col3 from test where col1=1 and col2=2。那么MySQL可以直接通过遍历索引取得数据,而无需回表,这减少了很多的随机io操作。

  • 效率高。索引列越多,通过索引筛选出的数据越少。有1000W条数据的表,有如下SQL:select from table where col1=1 and col2=2 and col3=3,假设假设每个条件可以筛选出10%的数据,如果只有单值索引,那么通过该索引能筛选出1000W *10%=100w条数据,然后再回表从100w条数据中找到符合col2=2 and col3= 3的数据,然后再排序,再分页;如果是联合索引,通过索引筛选出1000w *10% *10% *10%=1w,效率提升可想而知!

举例 如有索引(a, b, c, d),查询条件a = 1 and b = 2 and c > 3 and d = 4,则会在每个节点依次命中a、b、c,无法命中d。(会一直向右匹配直到遇到范围查询(>,<,BETWEEN,LIKE)就停止匹配)

回表

每条记录在数据库有一个唯一的物理id,当查询数据时,在索引中查找索引后,获得该行的物理id,根据物理id再查询表中数据,就是回表。

覆盖索引

MySQL可以利用索引返回SELECT 列表中的字段,而不必根据索引再次读取数据文件。包含所有满足查询需要的数据的索引成为覆盖索引(Covering Index)。也就是平时所说的不需要回表操作,也就是说当前查询所需要的数据直接就可以在索引里面查得到。有时候根据业务,建立多列索引,使用覆盖索引,可以取得相当好的性能优化。

联合索引abc,查询bc真的不会走索引吗

如果没有其他可用的索引,导致需要全盘扫描,然后联合索引正好有query所需的所有字段,那mysql会选择全扫描那个索引,而不是回表,这是一个性能优化。

什么时候走索引、什么时候不走索引

TODO

MySQL中 redo log、undo log 和 binlog 区别

MySQL中有六种日志文件:重做日志(redo log)、回滚日志(undo log)、二进制日志(binlog)、错误日志(errorlog)、慢查询日志(slow query log)、一般查询日志(general log),中继日志(relay log)。

重做日志(redo log) 作用

  • 确保事务的持久性。

  • 防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。

内容 物理格式的日志,记录的是物理数据页面的修改的信息,其redo log是顺序写入redo log file的物理文件中去的。

什么时候产生 事务开始之后就产生redo log,redo log的落盘并不是随着事务的提交才写入的,而是在事务的执行过程中,便开始写入redo log文件中。

什么时候释放 当对应事务的脏页写入到磁盘之后,redo log的使命也就完成了,重做日志占用的空间就可以重用(被覆盖)。

回滚日志(undo log) 作用 保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。

内容 逻辑格式的日志,在执行undo的时候,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物理页面上操作实现的,这一点是不同于redo log的。 undo log主要存储的是逻辑日志,比如我们要insert一条数据了,那undo log会记录的一条对应的delete日志。我们要update一条记录时,它会记录一条对应相反的update记录

什么时候产生 事务开始之前,将当前是的版本生成undo log,undo 也会产生 redo 来保证undo log的可靠性

什么时候释放: 当事务提交之后,undo log并不能立马被删除,而是放入待清理的链表,由purge线程判断是否由其他事务在使用undo段中表的上一个事务之前的版本信息,决定是否可以清理undo log的日志空间。

二进制日志(binlog) 作用

  • 用于复制,在主从复制中,从库利用主库上的binlog进行重播,实现主从同步。

  • 用于数据库的基于时间点的还原。

内容 逻辑格式的日志,可以简单认为就是执行过的事务中的sql语句。但又不完全是sql语句这么简单,而是包括了执行的sql语句(增删改)反向的信息,也就意味着delete对应着delete本身和其反向的insert;update对应着update执行前后的版本的信息;insert对应着delete和insert本身的信息。可以基于binlog做到类似于oracle的闪回功能,其实都是依赖于binlog中的日志记录。

什么时候产生 事务提交的时候,一次性将事务中的sql语句(一个事物可能对应多个sql语句)按照一定的格式记录到binlog中。这里与redo log很明显的差异就是redo log并不一定是在事务提交的时候刷新到磁盘,redo log是在事务开始之后就开始逐步写入磁盘。因此对于事务的提交,即便是较大的事务,提交(commit)都是很快的,但是在开启了bin_log的情况下,对于较大事务的提交,可能会变得比较慢一些。这是因为binlog是在事务提交的时候一次性写入的造成的,这些可以通过测试验证。

什么时候释放 binlog的默认是保持时间由参数expire_logs_days配置,也就是说对于非活动的日志文件,在生成时间超过expire_logs_days配置的天数之后,会被自动删除。

重做日志(redo log)和二进制日志(binlog)区别

  1. 作用不同:redo log是保证事务的持久性的,是事务层面的,binlog作为还原的功能,是数据库层面的(当然也可以精确到事务层面的),虽然都有还原的意思,但是其保护数据的层次是不一样的

  2. 内容不同:redo log是物理日志,是数据页面的修改之后的物理记录,binlog是逻辑日志,可以简单认为记录的就是sql语句

  3. 另外,两者日志产生的时间,可以释放的时间,在可释放的情况下清理机制,都是完全不同的

  4. 恢复数据时候的效率,基于物理日志的redo log恢复数据的效率要高于语句逻辑日志的binlog

SQL执行机制

先是连接器,连接成功,如果用了缓存就还要查询缓存,缓存内能查到结果后面的就都不走了,直接返回,没有命中缓存就依次,分析器,优化器,执行器;执行器的步骤是这样的:从磁盘文件找到对应查询条件的整页数据加载到buffer pool,写入更新数据的旧值到uodo日志文件,更新buffer pool内加载的数据,写redo日志,准备提交事务 redo日志写入磁盘,准备提交事务 binlog日志写入磁盘,写入commit 标记到redo日志文件里 提交事务完成,buffer pool随机写入磁盘;大概就上面的步骤,是以更新操作来说的

主从同步

过程

  1. 主库写入数据并且生成binlog文件。该过程中MySQL将事务串行的写入二进制日志,即使事务中的语句都是交叉执行的。

  2. 在事件写入二进制日志完成后,master通知存储引擎提交事务。

  3. 从库服务器上的IO线程连接Master服务器,请求从执行binlog日志文件中的指定位置开始读取binlog至从库。

  4. 主库接收到从库的IO线程请求后,其上复制的IO线程会根据Slave的请求信息分批读取binlog文件然后返回给从库的IO线程。

  5. Slave服务器的IO线程获取到Master服务器上IO线程发送的日志内容、日志文件及位置点后,会将binlog日志内容依次写到Slave端自身的Relay Log(即中继日志)文件的最末端,并将新的binlog文件名和位置记录到master-info文件中,以便下一次读取master端新binlog日志时能告诉Master服务器从新binlog日志的指定文件及位置开始读取新的binlog日志内容。

  6. 从库服务器的SQL线程会实时监测到本地Relay Log中新增了日志内容,然后把RelayLog中的日志翻译成SQL并且按照顺序执行SQL来更新从库的数据。

  7. 从库在relay-log.info中记录当前应用中继日志的文件名和位置点以便下一次数据复制。

异步、全同步和半同步复制

异步复制(Asynchronous replication)

通常没说明指的都是异步,即主库执行完Commit后,在主库写入Binlog日志后即可成功返回客户端,无需等等Binlog日志传送给从库,一旦主库宕机,有可能会丢失日志。

全同步复制(Fully synchronous replication)

指当主库执行完一个事务,所有的从库都执行了该事务才返回给客户端。因为需要等待所有从库执行完该事务才能返回,所以全同步复制的性能必然会收到严重的影响。

半同步复制(Semisynchronous replication)

而半同步复制,是等待其中一个从库也接收到Binlog事务并成功写入Relay Log之后,才返回Commit操作成功给客户端;如此半同步就保证了事务成功提交后至少有两份日志记录,一份在主库Binlog上,另一份在从库的Relay Log上,从而进一步保证数据完整性;半同步复制很大程度取决于主从网络RTT(往返时延),以插件 semisync_master/semisync_slave 形式存在。

Last updated