Mysql数据库总结

总结自:JavaGuide,仅用于个人学习。

1.什么是Mysql

MySQL 是⼀种关系型数据库,在Java企业级开发中⾮常常⽤,因为 MySQL 是开源免费的,并且⽅便扩展。MySQL 的默认端⼝号是 3306。

MySQL 字符编码集中有两套 UTF-8 编码实现:utf8 和 utf8mb4。

  • utf8 : utf8编码只支持1-3个字节 。 在 utf8 编码中,中文是占 3 个字节,其他数字、英文、符号占一个字节。但 emoji 符号占 4 个字节,一些较复杂的文字、繁体字也是 4 个字节。
  • utf8mb4 : UTF-8 的完整实现,正版!最多支持使用 4 个字节表示字符,因此,可以用来存储 emoji 符号。

2.主键和外键有什么区别?

  • 主键(主码) :主键用于唯一标识一个元组,不能有重复,不允许为空。一个表只能有一个主键。一般选择id作为主键。
  • 外键(外码) :外键用来和其他表建立联系用,外键是另一表的主键,外键是可以有重复的,可以是空值。一个表可以有多个外键。

不得使用外键与级联,一切外键概念必须在应用层解决。
说明: 以学生和成绩的关系为例,学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群; 级联更新是强阻塞,存在数据库更新风暴的风 险; 外键影响数据库的插入速度

补充:
在这里插入图片描述

3.数据库范式

  1. 第一范式:确保表中每一列数据的原子性,不可再分!
  2. 第二范式:在满足第一范式的基础上,确保列数据要跟主键关联,不能出现部分依赖。
  3. 第三范式:再满足第二范式的基础上,保证每一列数据都要跟主键直接关联,不能出现传递依赖。

在设计数据库结构的时候,要尽量遵守三范式,如果不遵守,必须有足够的理由。比如性能。事实上我们经常会为了性能而妥协数据库的设计。

什么时候不遵守数据库三大范式
没有冗余的数据库未必是最好的数据库,有时为了提高运行效率,提高读性能,就必须降低范式标准,适当保留冗余数据。

具体做法是: 在概念数据模型设计时遵守第三范式,降低范式标准的工作放到物理数据模型设计时考虑。降低范式就是增加字段,减少了查询时的关联,提高查询效率,因为在数据库的操作中查询的比例要远远大于DML的比例。但是反范式化一定要适度,并且在原本已满足三范式的基础上再做调整的。


4.drop、delete 与 truncate 区别?

4.1 三者定义:

  • drop(丢弃数据): drop table 表名,直接将表都删除掉,在删除表的时候使用。
  • truncate (清空数据) : truncate table 表名,只删除表中的数据,再插入数据的时候自增长 id 又从 1 开始,在清空表中数据的时候使用。
  • delete(删除数据) : delete from 表名 where 列名=值,删除某一行的数据,如果不加 where 子句和 truncate table 表名 作用类似。

truncate 和不带 where 子句的 delete、以及 drop 都会删除表内的数据,但是 truncate 和 delete 只删除数据不删除表的结构(定义),执行 drop 语句,此表的结构也会删除,也就是执行 drop 之后对应的表不复存在。

一般来说执行速度比较:drop > truncate > delete

delete命令执行的时候会产生数据库的binlog日志,而日志记录是需要消耗时间的,但是也有个好处方便数据回滚恢复。

truncate命令执行的时候不会产生数据库日志,因此比delete要快。除此之外,还会把表的自增值重置和索引恢复到初始大小等。

drop命令会把表占用的空间全部释放掉。

4.2 区别

truncate 和 drop 属于 DDL(数据定义语言)语句,操作立即生效,原数据不放到 rollback segment 中,不能回滚,操作不触发 trigger。而 delete 语句是 DML (数据库操作语言)语句,这个操作会放到 rollback segement 中,事务提交之后才生效。

注意:

  • DML 是数据库操作语言(Data Manipulation Language)的缩写,是指对数据库中表记录的操作,主要包括表记录的插入(insert)、更新(update)、删除(delete)和查询(select),是开发人员日常使用最频繁的操作。
  • DDL (Data Definition Language)是数据定义语言的缩写,简单来说,就是对数据库内部的对象进行创建、删除、修改的操作语言。它和 DML 语言的最大区别是 DML 只是对表内部数据的操作,而不涉及到表的定义、结构的修改,更不会涉及到其他对象。DDL 语句更多的被数据库管理员(DBA)所使用,一般的开发人员很少使用。

5. 存储引擎

Mysql5.5 版本之后默认存储引擎为 InnoDB,只有 InnoDB 支持事务。

常用的存储引擎有以下:

  • Innodb引擎:Innodb引擎提供了对数据库ACID事务的支持。并且还提供了行级锁和外键的约束。它的设计的目标就是处理大数据容量的数据库系统。
  • MyIASM引擎(原本Mysql的默认引擎):不提供事务的支持,也不支持行级锁和外键。
  • MEMORY引擎:所有的数据都在内存中,数据的处理速度快,但是安全性不高。

5.1 MyISAM 与 InnoDB 区别

  1. 是否支持行级锁
    MyISAM 只有表级锁(table-level locking),而 InnoDB 支持行级锁(row-level locking) 和表级锁,默认为行级锁。

  2. 是否支持事务
    MyISAM 不提供事务支持。
    InnoDB 提供事务支持,具有提交(commit)和回滚(rollback)事务的能力。

  3. 是否支持外键
    MyISAM 不支持,而 InnoDB 支持。

  4. 是否支持数据库异常崩溃后的安全恢复
    MyISAM 不支持,而 InnoDB 支持。

  5. 是否支持 MVCC
    MyISAM 不支持,而 InnoDB 支持。
    MVCC 可以看作是行级锁的一个升级,可以有效减少加锁操作,提高性能。应对⾼并发事务, MVCC⽐单纯的加锁更⾼效;MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下⼯作;MVCC可以使⽤乐观(optimistic)锁 和 悲观(pessimistic)锁来实现;各数据库中MVCC实现并不统⼀。

补充:

  • 使用 InnoDB 的数据库在异常崩溃后,数据库重新启动的时候会保证数据库恢复到崩溃前的状态。这个恢复的过程依赖于 redo log 。
  • MySQL InnoDB 引擎使用 redo log(重做日志)保证事务的持久性,使用 undo log(回滚日志)来保证事务的原子性
  • MySQL InnoDB 引擎通过 锁机制、MVCC 等手段来保证事务的隔离性( 默认支持的隔离级别是 REPEATABLE-READ )。
  • 保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。

5.2 MyISAM 索引与 InnoDB 索引的区别

  • InnoDB索引是聚簇索引,MyISAM索引是非聚簇索引。
  • InnoDB的主键索引的叶子节点存储着行数据,因此主键索引非常高效。
  • MyISAM索引的叶子节点存储的是行数据地址,需要再寻址一次才能得到数据。
  • InnoDB非主键索引的叶子节点存储的是主键和其他带索引的列数据,因此查询时做到覆盖索引会非常高效。

5.3 锁机制与 InnoDB 锁算法

MyISAM 和 InnoDB 存储引擎使用的锁:

  • MyISAM 采用表级锁(table-level locking)。
  • InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁

表级锁和行级锁对比:

  • 表级锁: MySQL 中锁定 粒度最大 的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM 和 InnoDB 引擎都支持表级锁。
  • 行级锁: MySQL 中锁定 粒度最小 的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。

InnoDB 存储引擎的锁的算法有三种:

  • Record lock:记录锁,单个行记录上的锁
  • Gap lock:间隙锁,锁定一个范围,不包括记录本身
  • Next-key lock:record+gap 临键锁,锁定一个范围,包含记录本身

6.索引及其数据结构

索引是一种用于快速查询和检索数据的数据结构,可以对数据库表中的一列或者多列的值进行排序的一种结构。常见的索引结构有: B 树, B+树和 Hash。 索引的作用就相当于目录的作用。

# 创建索引
CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name (index_col_name,... ) ;
#eg: 
CREATE INDEX idx_user_name ON tb_user(name);
create index idx_user_age_phone on tb_user(age,phone);

6.1索引的优缺点:

优点 :

  • 使用索引可以大大加快数据的检索速度(大大减少检索的数据量), 这也是创建索引的最主要的原因。
  • 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。

缺点:

  • 建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
  • 索引需要使用物理文件存储,也会耗费一定空间。

6.2索引的底层数据结构:

1、哈希表
哈希表是键值对的集合,通过键(key)即可快速取出对应的值(value),因此哈希表可以快速检索数据(接近 O(1))。

但是!哈希算法有个 Hash 冲突 问题,也就是说多个不同的 key 最后得到的 index 相同。通常情况下,我们常用的解决办法是链地址,链地址法就是将哈希冲突数据存放在链表中。就比如 JDK1.8 之前 HashMap 就是通过链地址法来解决哈希冲突的。JDK1.8 以后HashMap为了减少链表过长的时候搜索时间过长引入红黑树

2. B 树& B+树
B 树也称 B-树,全称为 多路平衡查找树 ,B+ 树是 B 树的一种变体。

B 树& B+树两者有何异同呢?

  1. B 树的所有节点既存放键(key) 也存放 数据(data),而 B+树只有叶子节点存放 key 和 data,其他内节点只存放 key。所以查找相同数据量的情况下,B 树的⾼度更⾼,IO 更频繁。
  2. B 树的叶子节点都是独立的;B+树的叶子节点有一条引用链指向与它相邻的叶子节点。
  3. B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而 B+树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。

6.3 Mysql中使用的索引结构:B+树

Mysql不使用哈希表作为索引的原因?

  1. Hash 冲突问题 :我们上面也提到过Hash 冲突了,不过对于数据库来说这还不算最大的缺点。

  2. Hash 索引不支持顺序和范围查询(Hash 索引不支持顺序和范围查询是它最大的缺点: 假如我们要对表中的数据进行排序或者进行范围查询,那 Hash 索引可就不行了。

InnoDB 存储引擎有⼀个特殊的功能叫“⾃适应哈希索引”,当某个索引值被使⽤的⾮常频繁时,会在 B+ 树索引之上再创建⼀个哈希索引,这样就让 B+Tree 索引具有哈希索引的⼀些优点,⽐如:快速的哈希查找。

在 MySQL 中,MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构,但是,两者的实现方式不太一样。

  • MyISAM 引擎中,B+Tree 叶节点的 data 域存放的是数据记录的地址。在索引检索的时候,首先按照 B+Tree 搜索算法搜索索引,如果指定的 Key 存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。
  • InnoDB 引擎中,其数据文件本身就是索引文件。相比 MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按 B+Tree 组织的一个索引结构,树的叶节点 data 域保存了完整的数据记录。这个索引的 key 是数据表的主键,因此 InnoDB 表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”,而其余的索引都作为辅助索引,辅助索引的 data 域存储相应记录主键的值而不是地址,这也是和 MyISAM 不同的地方。在根据主索引搜索时,直接找到 key 所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。

6.4 索引类型

1. 主键索引(Primary Key)

  • 数据表的主键列使用的就是主键索引。
  • 一张数据表有只能有一个主键,并且主键不能为 null,不能重复。
  • 在 MySQL 的 InnoDB 的表中,当没有显示的指定表的主键时,InnoDB 会自动先检查表中是否有唯一索引且不允许存在null值的字段,如果有,则选择该字段为默认的主键,否则 InnoDB 将会自动创建一个 6Byte 的自增主键、

2.二级索引(辅助索引)
二级索引又称为辅助索引,是因为二级索引的叶子节点存储的数据是主键。也就是说,通过二级索引,可以定位主键的位置。
唯一索引,普通索引,前缀索引等索引属于二级索引。

  1. 唯一索引(Unique Key) :唯一索引也是一种约束。唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引。 建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
  2. 普通索引(Index) :普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和 NULL。
  3. 前缀索引(Prefix) :前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小, 因为只取前几个字符。
  4. 全文索引(Full Text) :全文索引主要是为了检索大文本数据中的关键字的信息,是目前搜索引擎数据库使用的一种技术。Mysql5.6 之前只有 MYISAM 引擎支持全文索引,5.6 之后 InnoDB 也支持了全文索引。

6.5 聚集索引与非聚集索引

6.5.1 聚集索引

聚集索引即索引结构和数据一起存放的索引。主键索引属于聚集索引。

聚簇索引是对磁盘上实际数据重新组织以按指定的⼀个或多个列的值排序的算法。特点是存储数据的顺序和索引顺序⼀致。

在 MySQL 中,InnoDB 引擎的表的 .ibd文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引(B+树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。

优点:

  • 聚集索引的查询速度非常的快,因为整个 B+树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。

缺点:

  1. 依赖于有序的数据 :因为 B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 UUID 这种又长又难比较的数据,插入或查找的速度肯定比较慢。
  2. 更新代价大 : 如果对索引列的数据被修改时,那么对应的索引也将会被修改,而且聚集索引的叶子节点还存放着数据,修改代价肯定是较大的,所以对于主键索引来说,主键一般都是不可被修改的。

6.5.2 非聚集索引

非聚集索引即索引结构和数据分开存放的索引。二级索引属于非聚集索引。
非聚集索引不一定回表查询。

非聚集索引的叶子节点并不一定存放数据的指针,因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。MyISAM引擎的B+树的非聚集索引的叶子节点存储的就是存放数据的指针。

优点:

  • 更新代价比聚集索引要小 。非聚集索引的更新代价就没有聚集索引那么大了,非聚集索引的叶子节点是不存放数据的。

缺点:

  1. 跟聚集索引一样,非聚集索引也依赖于有序的数据
  2. 可能会二次查询(回表) :这应该是非聚集索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。

6.6 覆盖索引

如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为“覆盖索引”。我们知道在 InnoDB 存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次。这样就会比较慢。覆盖索引就是把要查询出的列和索引是对应的,不做回表操作!

覆盖索引即需要查询的字段正好是索引的字段,那么直接根据该索引,就可以查到数据了, 而无需回表查询。

6.7 联合索引

使用表中的多个字段创建索引,就是 联合索引,也叫 组合索引 或 复合索引。

6.8 索引设计的原则

  1. 适合索引的列是出现在where子句中的列,或者连接子句中指定的列(较频繁作为查询条件的字段才去创建索引 )。
  2. 基数较小的类,查询中很少涉及的列,重复值比较多的列,索引效果较差,没有必要在此列建立索引。
  3. 使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间。
  4. 不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。
  5. 最左前缀法则。
    如果索引了多列(联合索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。如果跳跃某一列,索引将会部分失效(后面的字段索引失效)。
    注意 : 最左前缀法则中指的最左边的列,是指在查询时,联合索引的最左边的字段(即是第一个字段)必须存在,与我们编写SQL时,条件编写的先后顺序无关。
  6. 更新频繁字段不适合创建索引。
  7. 若是不能有效区分数据的列不适合做索引列(如性别,男女未知,多也就三种,区分度实在太低)。
  8. 尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
  9. 定义有外键的数据列一定要建立索引。
  10. 对于定义为 text、image 和 bit 的数据类型的列不要建立索引。

6.9 创建索引的注意事项

  1. 选择合适的字段创建索引:
  • 不为 NULL 的字段 :索引字段的数据应该尽量不为 NULL,因为对于数据为 NULL 的字段,数据库较难优化。如果字段频繁被查询,但又避免不了为 NULL,建议使用 0,1,true,false 这样语义较为清晰的短值或短字符作为替代。
  • 被频繁查询的字段 :我们创建索引的字段应该是查询操作非常频繁的字段。
  • 被作为条件查询的字段 :被作为 WHERE 条件查询的字段,应该被考虑建立索引。
  • 频繁需要排序的字段 :索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。
  • 被经常频繁用于连接的字段 :经常用于连接的字段可能是一些外键列,对于外键列并不一定要建立外键,只是说该列涉及到表与表的关系。对于频繁被连接查询的字段,可以考虑建立索引,提高多表连接查询的效率。
  1. 被频繁更新的字段应该慎重建立索引。

  2. 尽可能的考虑建立联合索引而不是单列索引。

  3. 注意避免冗余索引 。
    冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。

  4. 考虑在字符串类型的字段上使用前缀索引代替普通索引。
    前缀索引仅限于字符串类型,较普通索引会占用更小的空间,所以可以考虑使用前缀索引带替普通索引。

6.10 索引失效的情况

  1. 索引列运算
    不要在索引列上进行运算操作, 索引将失效。如使用substring函数。

  2. 字符串不加引号
    字符串类型字段使用时,不加引号,索引将失效。如果字符串不加单引号,对于查询结果,没什么影响,但是数据库存在隐式类型转换,索引将失效。

  3. 模糊查询
    如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。
    在like模糊查询中,在关键字后面加%,索引可以生效。而如果在关键字前面加了%,索引将会失效。

explain select * from tb_user where profession like '软件%';
explain select * from tb_user where profession like '%工程';
explain select * from tb_user where profession like '%工%';
# 查询软件工程的时候,只有第一个使用索引,其余两个索引失效。
  1. or连接条件
    用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。当or连接的条件,左右两侧字段都有索引时,索引才会生效。

  2. 数据分布影响
    如果MySQL评估使用索引比全表更慢,则不使用索引。
    因为MySQL在查询时,会评估使用索引的效率与走全表扫描的效率,如果走全表扫描更快,则放弃索引,走全表扫描。 因为索引是用来索引少量数据的,如果通过索引查询返回大批量的数据,则还不如走全表扫描来的快,此时索引就会失效。

  3. 联合索引中,出现范围查询(>,<, between和以%开头的like查询),范围查询右侧的列索引失效。

# 语法
create index idx_xxxx on table_name(column(n)) ;
# 为tb_user表的email字段,建立长度为5的前缀索引。
create index idx_email_5 on tb_user(email(5));

7. 事务

事务是逻辑上的一组操作,要么都执行,要么都不执行。
在这里插入图片描述
事务的四大特性(ACID):

  1. 原子性(Atomicity) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
  2. 一致性(Consistency): 执行事务前后,数据保持一致,多个事务对同⼀个数据读取的结果是相同的;例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
  3. 隔离性(Isolation): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
  4. 持久性(Durability): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

MySQL InnoDB 引擎使用 redo log(重做日志) 保证事务的持久性,使用 undo log(回滚日志) 来保证事务的原子性。
MySQL InnoDB 引擎通过 锁机制、MVCC 等手段来保证事务的隔离性( 默认支持的隔离级别是 REPEATABLE-READ )。
保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障

7.1 并发事务带来的问题

  • 脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
  • 不可重复读(Unrepeatable read): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
  • 幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

  • 丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务 1 读取某表中的数据 A=20,事务 2 也读取 A=20,事务 1 修改 A=A-1,事务 2 也修改 A=A-1,最终结果 A=19,事务 1 的修改被丢失。

不可重复读和幻读区别:

不可重复读的重点是修改⽐如多次读取⼀条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除⽐如多次读取⼀条记录发现记录增多或减少了。

7.2 事务的隔离级别

SQL 标准定义了四个隔离级别:

  • READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。(Mysql默认的隔离级别)
  • SERIALIZABLE(可串行化): 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

InnoDB 存储引擎在 分布式事务 的情况下一般会用到 SERIALIZABLE(可串行化) 隔离级别。


8.MySQL三大日志(binlog、redo log和undo log)详解

日志详解

MySQL 日志 主要包括错误日志、查询日志、慢查询日志、事务日志、二进制日志几大类。其中,比较重要的还要属二进制日志 binlog(归档日志)和事务日志 redo log(重做日志)和 undo log(回滚日志)

8.1 redo log日志

redo log(重做日志) 是InnoDB存储引擎独有的,它让MySQL拥有了崩溃恢复能力。

比如 MySQL 实例挂了或宕机了,重启时,InnoDB存储引擎会使用redo log恢复数据,保证数据的持久性与完整性。

MySQL 中数据是以页为单位,你查询一条记录,会从硬盘把一页的数据加载出来,加载出来的数据叫数据页,会放入到 Buffer Pool 中。后续的查询都是先从 Buffer Pool 中找,没有命中再去硬盘加载,减少硬盘 IO 开销,提升性能。

更新表数据的时候,也是如此,发现 Buffer Pool 里存在要更新的数据,就直接在 Buffer Pool 里更新。

然后会把“在某个数据页上做了什么修改”记录到重做日志缓存(redo log buffer)里,接着刷盘到 redo log 文件里。前者是在内存中,后者在磁盘中。

刷盘时机

InnoDB 存储引擎为 redo log 的刷盘策略提供了 innodb_flush_log_at_trx_commit 参数,它支持三种策略:

  • 0 :设置为 0 的时候,表示每次事务提交时不进行刷盘操作
  • 1 :设置为 1 的时候,表示每次事务提交时都将进行刷盘操作(默认值)
  • 2 :设置为 2 的时候,表示每次事务提交时都只把 redo log buffer 内容写入 page cache

存储形式

硬盘上存储的 redo log 日志文件不只一个,而是以一个日志文件组的形式出现的,每个的redo日志文件大小都是一样的。它采用的是环形数组形式,从头开始写,写到末尾又回到头循环写。

只要每次把修改后的数据页直接刷盘不就好了,还有 redo log 什么事??

1 Byte = 8bit
1 KB = 1024 Byte
1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB

实际上,数据页大小是16KB,刷盘比较耗时,可能就修改了数据页里的几 Byte 数据,有必要把完整的数据页刷盘吗?

而且数据页刷盘是随机写,因为一个数据页对应的位置可能在硬盘文件的随机位置,所以性能是很差。

如果是写 redo log,一行记录可能就占几十 Byte,只包含表空间号、数据页号、磁盘文件偏移 量、更新值,再加上是顺序写,所以刷盘速度很快。

所以用 redo log 形式记录修改内容,性能会远远超过刷数据页的方式,这也让数据库的并发能力更强。

8.2 binlog日志

binlog会记录所有涉及更新数据的逻辑操作,并且是顺序写。

redo log 它是物理日志,记录内容是“在某个数据页上做了什么修改”,属于 InnoDB 存储引擎。

而 binlog 是逻辑日志,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于MySQL Server 层。

可以说MySQL数据库的数据备份、主备、主主、主从都离不开binlog,需要依靠binlog来同步数据,保证数据一致性。

记录格式

binlog 日志有三种格式,可以通过binlog_format参数指定。

  • statement:记录SQL语句原文,执行什么写什么。但是对于一些比如时间之类的更新就会出现错误。
  • row:记录的内容不再是简单的SQL语句了,还包含操作的具体数据。row格式记录的内容看不到详细信息,要通过mysqlbinlog工具解析出来。就比如update_time=now()变成了具体的时间update_time=1627112756247。
  • mixed:MySQL会判断这条SQL语句是否可能引起数据不一致,如果是,就用row格式,否则就用statement格式

写入机制

binlog的写入时机也非常简单,事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件中。

因为一个事务的binlog不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为binlog cache。

我们可以通过binlog_cache_size参数控制单个线程 binlog cache 大小,如果存储内容超过了这个参数,就要暂存到磁盘(Swap)

8.3 binlog日志与redo log日志对比

redo log(重做日志)让InnoDB存储引擎拥有了崩溃恢复能力。

binlog(归档日志)保证了MySQL集群架构的数据一致性。

  • 在执行更新语句过程,会记录redo log与binlog两块日志,以基本的事务为单位,redo log在事务执行过程中可以不断写入,而binlog只有在提交事务时才写入,所以redo log与binlog的写入时机不一样。

8.4 undo log

回滚日志,用于记录数据被修改前的信息 , 作用包含两个 : 提供回滚(保证事务的原子性) 和MVCC(多版本并发控制) 。

undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo
log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的
update记录。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。


9.MVCC(多版本并发控制)

全称 Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,
使得读写操作没有冲突。 多版本并发控制(MVCC)是⼀种⽤来解决读-写冲突的⽆锁并发控制,也就是为事务分配单向增⻓的时间戳,为每个修改保存⼀个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。
快照读为MySQL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView。

数据库并发场景:

  1. 读-读:不存在任何问题,也不需要并发控制;
  2. 读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读;
  3. 写-写:有线程安全问题,可能会存在更新丢失问题。

在这里插入图片描述

mvcc用来解决读—写冲突的无锁并发控制,就是为事务分配单向增长的时间戳。为每个数据修改保存一个版本,版本与事务时间戳相关联.

解决问题如下:

  • 并发读-写时:可以做到读操作不阻塞写操作,同时写操作也不会阻塞读操作。
  • 解决脏读、幻读、不可重复读等事务隔离问题,但不能解决上面的写-写 更新丢失问题。

因此有下面提高并发性能的方法:

  • MVCC + 悲观锁:MVCC解决读写冲突,悲观锁解决写写冲突
  • MVCC + 乐观锁:MVCC解决读写冲突,乐观锁解决写写冲突

当前读(悲观锁的一种操作)

它读取的数据库记录,都是当前最新的版本,会对当前读取的数据进行加锁,防止其他事务修改数据。是悲观锁的一种操作。

如下操作都是当前读:

  • select lock in share mode (共享锁)
  • select for update (排他锁)
  • update (排他锁)
  • insert (排他锁)
  • delete (排他锁)
  • 串行化事务隔离级别

快照读

快照读的实现是基于多版本并发控制,即MVCC,既然是多版本,那么快照读读到的数据不一定是当前最新的数据,有可能是之前历史版本的数据。

如下操作是快照读:

  • 不加锁的select操作(注:事务级别不是串行化)

9.1 隐藏字段

在创建表的时候,InnoDB会给我们添加三个隐藏字段:

隐藏字段含义
DB_TRX_ID最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID。
DB_ROLL_PTR回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个版本。
DB_ROW_ID隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。

而上述的前两个字段是肯定会添加的, 是否添加最后一个字段DB_ROW_ID,得看当前表有没有主键,
如果有主键,则不会添加该隐藏字段。

9.2 版本链

DB_TRX_ID是当前操作该记录的事务ID,而DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个旧版本。

每次对数据库记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表。

对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id(trx_id),这个信息很重要,在根据ReadView判断版本可见性的时候会用到。

9.3 undo log

Undo log 主要用于记录数据被修改之前的日志,在表信息修改之前先会把数据拷贝到undo log里。
当事务进行回滚时可以通过undo log 里的日志进行数据还原。

Undo log 的用途

  • 保证事务进行rollback时的原子性和一致性,当事务进行回滚的时候可以用undo log的数据进行恢复。
  • 用于MVCC快照读的数据,在MVCC多版本控制中,通过读取undo log的历史版本数据可以实现不同事务版本号都拥有自己独立的快照数据版本。

undo log主要分为两种:

  • insert undo log
    代表事务在insert新记录时产生的undo log , 只在事务回滚时需要,并且在事务提交后可以被立即丢弃

  • update undo log(主要)
    事务在进行update或delete时产生的undo log ; 不仅在事务回滚时需要,在快照读时也需要;
    所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

9.4 Read View(读视图)

ReadView(读视图)是 快照读 SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务
(未提交的)id。

Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。

ReadView中包含了四个核心字段:

字段含义
m_ids当前活跃的事务ID集合
min_trx_id最小活跃事务ID
max_trx_id预分配事务ID,当前最大事务ID+1(因为事务ID是自增的)
creator_trx_idReadView创建者的事务ID

在readview中就规定了版本链数据的访问规则:

其中,trx_id 代表当前undolog版本链对应事务ID。

(1)trx_id < min_trx_id || trx_id == creator_trx_id(显示)

  • 如果数据事务ID(trx_id)小于read view中的最小活跃事务ID(min_trx_id),则可以肯定该数据是在当前事务启之前就已经存在了的,所以可以显示
  • 或者数据的事务ID(trx_id)等于creator_trx_id ,那么说明这个数据就是当前事务自己生成的,自己生成的数据自己当然能看见,所以这种情况下此数据也是可以显示的。

(2)trx_id >= max_trx_id(不显示)

  • 如果数据事务ID(trx_id)大于read view 中的当前系统的最大事务ID,则说明该数据是在当前read view 创建之后才产生的,所以数据不显示。

(3)如果min_trx_id <= trx_id<= max_trx_id, 则进入下一个判断,判断trx_id是否在活跃事务(m_ids)中

不存在:则说明read view产生的时候事务已经commit了,这种情况数据则可以显示。
已存在:则代表我Read View生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的。

不同的隔离级别,生成ReadView的时机不同:

  • READ COMMITTED :在事务中每一次执行快照读时生成ReadView。
  • REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。

10. 大表优化

当MySQL单表记录数过⼤时,数据库的CRUD性能会明显下降,⼀些常⻅的优化措施如下:

1. 限定数据的范围
务必禁⽌不带任何限制数据范围条件的查询语句。⽐如:我们当⽤户在查询订单历史的时候,我
们可以控制在⼀个⽉的范围内。

  • 只返回必要的列:最好不要使⽤ SELECT * 语句;
  • 只返回必要的⾏:使⽤ LIMIT 语句来限制返回的数据

2. 读/写分离:
经典的数据库拆分⽅案,主库负责写,从库负责读

3. 垂直分区:
根据数据库⾥⾯数据表的相关性进⾏拆分。 例如,⽤户表中既有⽤户的登录信息⼜有⽤户的基本信息,可以将⽤户表拆分成两个单独的表,甚⾄放到单独的库做分库。
简单来说垂直拆分是指数据表列的拆分,把⼀张列⽐较多的表拆分为多张表。

优点:
可以使得列数据变⼩,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。
缺点:
主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应⽤层进⾏Join来解决。此外,垂直分区会让事务变得更加复杂;

4. 水平分区:
保持数据表结构不变,通过某种策略存储数据分⽚。这样每⼀⽚数据分散到不同的表或者库中,达到了分布式的⽬的。 ⽔平拆分可以⽀撑⾮常⼤的数据量。水平拆分最好分库进行。

数据库分片的两种常见方案:

  • 客户端代理: 分⽚逻辑在应⽤端,封装在jar包中,通过修改或者封装JDBC层来实现。 当当⽹的 Sharding-JDBC 、阿⾥的TDDL是两种⽐较常⽤的实现。
  • 中间件代理: 在应⽤和数据中间加了⼀个代理层。分⽚逻辑统⼀维护在中间件服务中。 我们现在谈的 Mycat 、360的Atlas、⽹易的DDB等等都是这种架构的实现。

5. 查询字段加上索引


11.什么是数据库连接池,池化思想说明下。

池化设计会初始预设资源,解决的问题就是抵消每次获取资源的消耗,如创建线程的开销,获取远程连接的开销等。除了初始化资源,池化设计还包括如下这些特征:池⼦的初始值、池⼦的活跃值、池⼦的最⼤值等,这些特征可以直接映射到java线程池和数据库连接池的成员属性中。

我们可以把数据库连接池是看做是维护的数据库连接的缓存,以便将来需要对数据库的请求时可以重⽤这些连接。为每个⽤户打开和维护数据库连接,尤其是对动态数据库驱动的⽹站应⽤程序的请求,既昂贵⼜浪费资源。在连接池中,创建连接后,将其放置在池中,并再次使⽤它时,不必建⽴新的连接。如果使⽤了所有连接,则会建⽴⼀个新连接并将其添加到池中。 连接池还减少了⽤户必须等待建⽴与数据库的连接的时间。


12.分库分表之后,id 主键如何处理?

因为要是分成多个表之后,每个表都是从 1 开始累加,这样是不对的,我们需要⼀个全局唯⼀的
id 来⽀持.

⽣成全局 id 有下⾯这⼏种⽅式:

  • UUID:不适合作为主键,因为太⻓了,并且⽆序不可读,查询效率低。⽐较适合⽤于⽣成
    唯⼀的名字的标示⽐如⽂件的名字。
  • 数据库⾃增 id : 两台数据库分别设置不同步⻓,⽣成不重复ID的策略来实现⾼可⽤。这种⽅
    式⽣成的 id 有序,但是需要独⽴部署数据库实例,成本⾼,还会有性能瓶颈。
  • 利⽤ redis ⽣成 id : 性能⽐较好,灵活⽅便,不依赖于数据库。但是,引⼊了新的组件造成
    系统更加复杂,可⽤性降低,编码更加复杂,增加了系统成本。
  • Twitter的snowflake算法 :Github 地址:https://github.com/twitter-archive/snowflake。
  • 美团的Leaf分布式ID⽣成系统 :Leaf 是美团开源的分布式ID⽣成器,能保证全局唯⼀性、
    趋势递增、单调递增、信息安全,⾥⾯也提到了⼏种分布式⽅案的对⽐,但也需要依赖关系
    数据库、Zookeeper等中间件。感觉还不错。美团技术团队的⼀篇⽂章:https://tech.meitua
    n.com/2017/04/21/mt-leaf.html 。

13.一条SQL语句执行很慢的原因有哪些?

分两种情况讨论:
(1)大多数情况是正常的,只是偶尔会出现很慢的情况。
(2)在数据量不变的情况下,这条SQL语句一直以来都执行的很慢。

1.针对偶尔很慢的情况

(1)数据库在刷新脏页(flush):将更新后的数据写入到磁盘中

有以下两种情况是需要刷新脏页:

  • redo log写满了: redo log 里的容量是有限的,如果数据库一直很忙,更新又很频繁,这个时候 redo log 很快就会被写满了,这个时候就没办法等到空闲的时候再把数据同步到磁盘的,只能暂停其他操作,全身心来把数据同步到磁盘中去的,而这个时候,就会导致我们平时正常的SQL语句突然执行的很慢,所以说,数据库在在同步数据到磁盘的时候,就有可能导致我们的SQL语句执行的很慢了。
  • 内存不够用了: 如果一次查询较多的数据,恰好碰到所查数据页不在内存中时,需要申请内存,而此时恰好内存不足的时候就需要淘汰一部分内存数据页,如果是干净页,就直接释放,如果恰好是脏页就需要刷脏页。

(2)拿不到锁
这个就比较容易想到了,我们要执行的这条语句,刚好这条语句涉及到的表,别人在用,并且加锁了,我们拿不到锁,只能慢慢等待别人释放锁了。

2. 针对一直都这么慢的情况
(1)没用上索引:字段没有索引;有索引却没有用上。
(2)数据库选错索引


14.SQL语句执行流程

  1. 应⽤程序把查询 SQL 语句发送给服务器端执⾏;
  2. 查询缓存,如果查询缓存是打开的,服务器在接收到查询请求后,并不会直接去数据库查询,⽽是在数据库的查询缓存中找是否有相对应的查询数据,如果存在,则直接返回给客户端。只有缓存不存在时,才会进⾏下⾯的操作;
  3. 查询优化处理,⽣成执⾏计划。这个阶段主要包括解析 SQL、预处理、优化 SQL 执⾏计划;
  4. MySQL 根据相应的执⾏计划完成整个查询;
  5. 将查询结果返回给客户端。

15. char与varchar的区别

  • char(n) :固定⻓度类型,⽐如:char(10),当你输⼊"abc"三个字符的时候,它们占的空间还是 10 个字节,其他 7 个是空字节。char 优点:效率⾼;缺点:占⽤空间;适⽤场景:存储密码的 md5 值,固定⻓度的,使⽤char ⾮常合适。
  • varchar(n) :可变⻓度,存储的值是每个值占⽤的字节再加上⼀个⽤来记录其⻓度的字节的⻓度。

varchar(10)与varchar(20)的区别:
varchar(10) 中 10 的含义最多存放 10 个字符,varchar(10) 和 varchar(20) 存储 hello 所占空间⼀样,但后者在排序时会消耗更多内存,因为 order by col 采⽤ fixed_length 计算 col ⻓度

所以,从空间上考虑 varcahr ⽐较合适;从效率上考虑 char ⽐较合适,⼆者使⽤需要权衡。


16. 谈谈你对⽔平切分和垂直切分的理解?

  • ⽔平切分
    ⽔平切分是将同⼀个表中的记录拆分到多个结构相同的表中。当⼀个表的数据不断增多时,⽔平切分是必然的选择,它可以将数据分布到集群的不同节点上,从⽽缓存单个数据库的压⼒。

  • 垂直切分
    垂直切分是将⼀张表按列切分成多个表,通常是按照列的关系密集程度进⾏切分,也可以利⽤垂直切分将经常被使⽤的列和不经常被使⽤的列切分到不同的表中。例如:将原来的电商数据库垂直切分成商品数据库、⽤户数据库等。


17.主从复制中涉及到哪三个线程?

Mysql主从复制是指一台服务器充当主数据库服务器,另一台或多台服务器充当从数据库服务器,主从复制的基础是主服务器对数据库修改记录二进制日志,从服务器通过主服务器的二进制日志自动执行更新。

主要涉及三个线程:binlog 线程、I/O 线程和 SQL 线程。

  1. binlog 线程 :负责将主服务器上的数据更改写⼊⼆进制⽇志(Binary log)中。
  2. I/O 线程 :负责从主服务器上读取⼆进制⽇志,并写⼊从服务器的重放⽇志(Relay log)中。
  3. SQL 线程 :负责读取重放⽇志并重放其中的 SQL 语句。
    在这里插入图片描述

18. 主从同步的延迟原因及解决办法?

主从同步的延迟的原因:

假如⼀个服务器开放 N 个连接给客户端,这样有会有⼤并发的更新操作, 但是从服务器的⾥⾯读取 binlog 的线程仅有⼀个, 当某个 SQL 在从服务器上执⾏的时间稍⻓或者由于某个 SQL 要进⾏锁表就会导致主服务器的 SQL ⼤量积压,未被同步到从服务器⾥。这就导致了主从不⼀致, 也就是主从延迟。

主从同步延迟的解决办法:

  1. 我们知道因为主服务器要负责更新操作, 它对安全性的要求⽐从服务器⾼,所有有些设置可以修改,⽐如sync_binlog=1,innodb_flush_log_at_trx_commit = 1 之类的设置,⽽ slave 则不需要这么⾼的数据安全,完全可以将 sync_binlog 设置为 0 或者关闭 binlog、innodb_flushlog、 innodb_flush_log_at_trx_commit 也可以设置为 0 来提⾼ SQL 的执⾏效率。
  2. 增加从服务器,这个⽬的还是分散读的压⼒, 从⽽降低服务器负载。

19.谈谈你对数据库读写分离的理解?

读写分离常⽤代理⽅式来实现,代理服务器接收应⽤层传来的读写请求,然后决定转发到哪个服务器。主服务器理写操作以及实时性要求⽐较⾼的读操作,⽽从服务器处理读操作。

读写分离能提⾼性能的原因在于:

  1. 主从服务器负责各⾃的读和写,极⼤程度缓解了锁的争⽤;
  2. 从服务器可以使⽤ MyISAM,提升查询性能以及节约系统开销;
  3. 增加冗余,提⾼可⽤性。

20.说⼀下 MySQL 的行锁和表锁?

MyISAM 只⽀持表锁,InnoDB ⽀持表锁和⾏锁,默认为⾏锁。

  • 表级锁:开销⼩,加锁快,不会出现死锁。锁定粒度⼤,发⽣锁冲突的概率最⾼,并发量最低。
  • ⾏级锁:开销⼤,加锁慢,会出现死锁。锁⼒度⼩,发⽣锁冲突的概率⼩,并发度最⾼。

21.InnoDB 存储引擎的锁的算法有哪些?

  1. Record lock:单个行记录上的锁
  2. Gap lock:间隙锁,锁定一个范围,不包括记录本身
  3. Next-key lock:record+gap 锁定一个范围,包含记录本身

相关知识点:

  1. innodb对于行的查询使用next-key lock。
  2. Next-locking keying为了解决Phantom Problem幻读问题。
  3. 当查询的索引含有唯一属性时,将next-key lock降级为record key。
  4. Gap锁设计的目的是为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生。
  5. 有两种方式显式关闭gap锁: 将事务隔离级别设置为RC 或者 将参数innodb_locks_unsafe_for_binlog设置为1。

22. MySQL 问题排查都有哪些手段?

  1. 使⽤ show processlist 命令查看当前所有连接信息;
  2. 使⽤ Explain 命令查询 SQL 语句执⾏计划;
  3. 开启慢查询⽇志,查看慢查询的 SQL。

23. 数据库的悲观锁和乐观锁

乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。

23.1 悲观锁

  • 定义:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。
  • 实现方式:使用数据库中的锁机制。synchronized就属于悲观锁的一种实现方式,每次线程要修改数据时都先获得锁,保证同一个时刻只有一个线程能操作数据,其他线程会被block。
  • 适用场景:多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

23.2 乐观锁

  • 定义:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。
  • 实现方式:乐观锁一般会使用版本号机制或CAS算法实现。
    1. 使用数据版本记录机制实现。为数据库添加一个数字类型的version字段来实现,判断version是否一致。
    2. 实现时间戳实现,在表中增加一个字段,字段类型为时间戳,在更新提交的时候检查当前数据库中的时间戳和自己更新前取到的时间戳是否一致,判断是否版本冲突。
  • 适用场景:写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。

24. JDBC如何进行事务管理

Connection提供了事务的处理方法,通过调用setAutoCommit(false)可以设置手动提交事务;当事务完成后用commit()显式提交事务;如果在事务处理过程中发生异常则通过rollback()进行事务回滚。


25.JDBC的反射,反射的都是什么?

通过反射com.mysql.jdbc.Driver类,实例化该类的时候会执行该类内部的静态代码块,该代码块会在Java实现的DriverManager类中注册自己,DriverManager会管理所有已经注册的驱动类,调用DriverManager.getConnection方法去遍历驱动类,实现连接数据库。


26.Statement和PrepareStatement的区别

都是接口,但PrepareStatement 继承了Statement。

  1. PrepareStatement实例包含已经编译的SQL语句,会将SQL语句进行预编译,所以执行速度比Statement高。prepareStatment事先对语句进行预处理,效率会高很多。Statment没有进行预处理,所以每次都要载入语句,所以效率比较低。
  2. PrepareStatement对象中的SQL语句可以包含一个或者多个参数,在SQL语句创建时可以不指定参数,可以为参数位置保留一个?作为占位符,每个问号的值必须在该语句执行前通过适当的setInt()或者setString()方法来提供。
  3. 用PrepareStatement可以防止sql注入,而Statement不行。

27. 视图

为了提高复杂 SQL 语句的复用性和表操作的安全性, MySQL数据库管理系统提供了视图特性。
视图使开发者只关心感兴趣的某些特定数据和所负责的特定任务,只能看到视图中所定义的数据,而不是视图所引用表中的数据,从而提高了数据库中数据的安全性。

视图的特点:

  • 视图的列可以来自不同的表,是表的抽象和在逻辑意义上建立的新关系。
  • 视图是由基本表(实表)产生的表(虚表)。
  • 视图的建立和删除不影响基本表。
  • 对视图内容的更新(添加,删除和修改)直接影响基本表。
  • 当视图来自多个基本表时,不允许添加和删除数据。视图的操作包括创建视图,查看视图,删除视图和修改视图。

视图的使用场景:

  • 简化复杂的SQL操作。在编写查询后,可以方便的重用它而不必知道它的基本查询细节;使用表的组成部分而不是整个表。
  • 保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限。
  • 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。

视图的优点:

  • 查询简单化。视图能简化用户的操作。
  • 数据安全性。视图使用户能以多种角度看待同一数据,能够对机密数据提供安全保护。
  • 逻辑数据独立性。视图对重构数据库提供了一定程度的逻辑独立性。

视图的缺点:

  • 性能。数据库必须把视图的查询转化成对基本表的查询,如果这个视图是由一个复杂的多表查询所定义,那么,即使是视图的一个简单查询,数据库也把它变成一个复杂的结合体,需要花费一定的时间。
  • 修改限制。当用户试图修改视图的某些行时,数据库必须把它转化为对基本表的某些行的修改。事实上,当从视图中插入或者删除时,情况也是这样。对于简单视图来说,这是很方便的,但是,对于比较复杂的视图,可能是不可修改的。这些视图有如下特征:
    • 1.有UNIQUE等集合操作符的视图。
    • 2.有GROUP BY子句的视图。
    • 3.有诸如AVG\SUM\MAX等聚合函数的视图。
    • 4.使用DISTINCT关键字的视图。
    • 5.连接表的视图(其中有些例外)。

视图与表的区别:

  • 视图是虚拟的表,视图展示的数据来自于 基本表。
  • 视图中不存储具体的数据,而存储的是sql逻辑,基本表的数据发生改变,视图的展示结果也会随着发生改变,它占用少量的物理空间;而表中保存的是数据,占用大的物理空间;
  • 对视图的操作跟普通表是一样的,如:创建视图,删除视图。。视图的建立create和删除drop只影响视图本身,不影响对应的基本表。
  • 若视图中的字段数据是来自于基表的话,一般是可以对视图中的数据 进行更新的,对视图数据进行添加、删除和修改操作会直接影响基本表。其他情况不允许更新,如:若视图中的字段使用了函数avg(age)等,就不能进行更新;
  • 视图的使用:一般都是把基表的子查询sql语句 封装成视图,方便使用,而且更高效。

28. 触发器

触发器是用户定义在关系表上的一类由事件驱动的特殊的存储过程。触发器是指一段代码,当触发某个事件时,自动执行这些代码。

使用场景 :

  • 可以通过数据库中的相关表实现级联更改。
  • 实时监控某张表中的某个字段的更改而需要做出相应的处理。
  • 例如可以生成某些业务的编号。
  • 注意不要滥用,否则会造成数据库及应用程序的维护困难。

MySQL 中常见触发器:

  • Before Insert
  • After Insert
  • Before Update
  • After Update
  • Before Delete
  • After Delete

29. SQL常见的一些约束

  • NOT NULL: 用于控制字段的内容一定不能为空(NULL)。
  • UNIQUE: 控件字段内容不能重复,一个表允许有多个 Unique 约束。
  • PRIMARY KEY: 也是用于控件字段内容不能重复,但它在一个表只允许出现一个。
  • FOREIGN KEY: 用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一。
  • CHECK: 用于控制字段的值范围。

30. 关联查询

  • 交叉连接(CROSS JOIN):不加查询条件,会造成笛卡尔积。
  • 内连接(INNER JOIN):分为等值连接、不等值连接、自连接。
  • 外连接(LEFT JOIN/RIGHT JOIN):左外连接(以左表为主,先查询出左表,按照ON后的关联条件匹配右表,没有匹配到的用NULL填充)、右外连接(以右表为主,先查询出右表,按照ON后的关联条件匹配左表,没有匹配到的用NULL填充)。
  • 联合查询(UNION与UNION ALL):是把多个结果集集中在一起,UNION前的结果为基准,需要注意的是联合查询的列数要相等,相同的记录行会合并。
  • 全连接(FULL JOIN):MySQL不支持全连接,可以使用LEFT JOIN + UNION + RIGHT JOIN联合使用。

31. 子查询

  • 条件:一条SQL语句的查询结果做为另一条查询语句的条件或查询结果
  • 嵌套:多条SQL语句嵌套使用,内部的SQL查询语句称为子查询。

子查询的三种情况:

  1. 子查询是单行单列的情况:结果集是一个值,父查询使用:=、 <、 > 等运算符。
  2. 子查询是多行单列的情况:结果集类似于一个数组,父查询使用:in 运算符。
  3. 子查询是多行多列的情况:结果集类似于一张虚拟表,不能用于where 条件,用于select子句中做为子表。

32. SQL的生命周期

  1. 应用服务器与数据库服务器建立一个连接
  2. 数据库进程拿到请求sql
  3. 解析并生成执行计划,执行
  4. 读取数据到内存并进行逻辑处理
  5. 通过步骤一的连接,发送结果到客户端
  6. 关掉连接,释放资源

33. 慢查询都怎么优化

慢查询的优化首先要搞明白慢的原因是什么。
是查询条件没有命中索引?是 load 了不需要的数据列?还是数据量太大?

  • 首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
  • 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
  • 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。

34. 对于SQL语句优化方法

  1. 优化查询过程中的数据访问:重写SQL语句,让优化器可以以更优的方式执行查询。
  2. 优化长难的查询语句:将一个大的查询分为多个小的相同的查询。
  3. 优化特定类型的查询语句:可以使用explain查询近似值,用近似值替代count(*) 增加汇总表使用缓存。
  4. 优化关联查询:确定ON或者USING子句中是否有索引。确保GROUP BY和ORDER BY只有一个表中的列,这样MySQL才有可能使用索引。
  5. 优化子查询:用关联查询替代GROUP BY和DISTINCT这两种查询,可以使用索引来优化。
  6. 优化LIMIT分页:LIMIT偏移量大的时候,可以记录上次查询的大ID,下次直接俄使用。
  7. 优化UNION查询:UNION ALL的效率高于UNION。
  8. 优化WHERE子句 :where字句中最好不要 对字段进行 null 值判断、使用!=或<>操作符、中使用or 来连接条件、使用like操作符、使用参数、对字段进行表达式操作,否则都会导致引擎放弃使用索引而进行全表扫描。

35. MYSQL中锁的类型

  • 乐观锁 :用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加1。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比 对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
  • 悲观锁:在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java synchronized很相似,共享锁(读锁)和排它锁(写锁)是悲观锁的不同的实现。
  • 共享锁(读锁):共享锁又叫做读锁,所有的事务只能对其进行读操作不能写操作,加上共享锁后在事务结束之前其他事务只能再加共享锁,除此之外其他任何类型的锁都不能再加了。
  • 排它锁(写锁):若某个事务对某一行加上了排他锁,只能这个事务对其进行读写,在此事务结束之前,其他事务不能对其进行加任何锁,其他进程可以读取,不能进行写操作,需等待其释放。
  • 表级锁:innodb 的行锁是在有索引的情况下,没有索引的表是锁定全表的 。
  • 行级锁 :行锁又分共享锁和排他锁,由字面意思理解,就是给某一行加上锁,也就是一条记录加上锁。 注意:行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁。
  • 页级锁:页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。
  • 死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

36. 死锁的产生、条件、如何避免死锁

产生死锁的原因主要是:

  1. 因为系统资源不足。
  2. 进程运行推进的顺序不合适。
  3. 资源分配不当等。

产生死锁的四个必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

如何避免线程死锁:

  1. 破坏互斥条件 :这个条件我们没有办法破坏,因为我们⽤锁本来就是想让他们互斥的(临界资源需要互斥访问)。
  2. 破坏请求与保持条件 :⼀次性申请所有的资源。
  3. 破坏不剥夺条件 :占⽤部分资源的线程进⼀步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  4. 破坏循环等待条件 :靠按序申请资源来预防。按某⼀顺序申请资源,释放资源则反序释放。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值