MySql

所有原图链接,未经允许所有内容都可都可都可转载,不对的地方欢迎指正 https://www.processon.com/mindmap/612211871e08534545453e07 https://www.processon.com/diagraming/612ce0615653bb2d6d186239

CHAR和VARCHAR的区别

  1. CHAR的长度不可变,而VARCHAR的长度是可变的。
  2. 存取,定义一个char[10] 和 varchar[10]. 存入一个字符串 abcd
    1. char所占的长度依然为10. abcd后面跟了6个空格。
    2. 而varchar立马就把长度变为4了。
    3. 取数据时,char需要把取出来的数据 trim去掉空格。
    4. 而varchar因为存储的时候没有加空格,因此也不需要trim.
  3. 空间,由于char会增加空格,所以会浪费磁盘空间。即varchar是时间换空间。
  4. 速度,char定长,方便程序存储和查找。即char是空间换时间。
  5. 存储方式,char 英文1字节,汉字2字节。

vharchar 英文汉字都是2字节

MYSQL支持事物码?

  1. 在缺省模式下,mysql是autocommit模式的。所有的数据库操作都会即使提交。
  2. 所以在缺省模式下,mysql是不支持事物的。
  3. 如果使用的存储引擎是innodb,支持事物,使用begin开启。
  4. 或者SET AUTOCOMMIT=0关闭自动提交。

    NOW和CURRENT_DARE()有什么区别

  5. NOW() 包含年月日,时分秒。

  6. CURRENT_DATE(),只有年月日,没有时分秒。

    BLOB和TEXT有什么区别

  7. BLOB是一个二级制对象

  8. TEXT是一个不区分大小写的BLOB
  9. BLOB排序是区分大小写,TEXT不区分。

    SQL语言包含哪几部分,每部分有哪些操作关键字。

    数据查询语言,DQL:Data Query Language

  10. 数据检索语句,用于从数据库表中查询数据。

  11. SELECT关键字

    数据操作语言。DML:DATA Manipulation Language

  12. 添加、删除、修改数据

  13. INSERT、UPDATE、DELETE

    事物处理语言 TPL

  14. 他的语句能确保被DML语句影响的表的所有行即使得到更新。

  15. TPL包括 BEGIN、TRANSACTION、COMMIT、ROLLBACK。

    数据控制语言 DCL

  16. 他的语句通过GRANT或者REVOKE获取的许可,确定单个用户和用户组队数据库对象的访问。

  17. 某些RDBMS可用GRANT或者REVOKE控制对表单个列的访问。

    数据定义语言 DDL

  18. 其语句包括GREATE和DROP.

  19. 在数据库创建新表、删除表、修改表结构、为表加入索引等。

    指针控制语言 CCL

  20. 对一个表单独行的操作。

    索引对数据库的负面影响是什么

  21. 创建索引和维护索引都需要耗费时间,这个随着数据量的增加而增加。

  22. 索引需要占用物理空间,不光是表需要占用物理空间。每个索引也需要占物理空间。
  23. 当对表进行增删改操作时索引也需要动态维护,这个将降低了数据的维护速度。

    主键索引、唯一索引、普通索引、全文索引、组合索引区别

    PRIMARY KEY主键索引

  24. alter table table_name add parmary key ( column_name)

  25. 索引列值必须唯一,不允许空值。

    UNIQUE 唯一索引

  26. alter table table_name add unique ( column_name)

  27. 与普通索引类似,索引列值必须唯一,允许空值

    普通索引

  28. alter table table_name add index index_name ( column_name)

  29. 最基本的索引,没有任何限制

    组合索引

  30. alter table table_name add index index_name ( column_name1, column_name2, column_name3)

  31. 多字段组合索引,遵循最左匹配。

    全文索引

  32. alter table table_name add fulltext ( column_name)

  33. 基本不适用,如果有这种场景需要考虑ES.

    索引的建立原则

    总体原则:查询更快,占用表空间更小

  34. 需要查询的列才需要建立索引,如果经常出现在where、jion on 后面的语句需要考虑建立索引。

  35. 表中数据达到一定量级才需要建立索引, 如果表中数据很少,没必要建立索引。
  36. 使用短索引,如果字符串长度过长,需要考虑下指定一个前缀长度,这样可以节省大量的表空间。
    1. 语法 create index index_name on table_name (cloumn_name(length),cloumn_name2(length)…)
    2. 确定当前字段设置合适长度,即需要找到 前缀/总数 百分比最大,前缀最短, select count(distinct left(cloumn_name, 5))/count(1) from table_name.
  37. 不要过度索引,索引需要占用磁盘空间、且数据的增加删除修改都需要维护索引。mysql单表建立索引也有个数限制16. 只要保持有利查询即可,如果所有列都需要索引。考虑下标设计的是否合理。ES。
  38. 频繁更新的字段不太适合建立索引,因为频繁地修改涉及到底层B+Tree的不断变动。
  39. 区分度太低的列、重复值太多的列,不适合建立索引,比如 男、女。

    索引失效

    查询条件包含or,可能导致索引失效

  40. where条件中两个字段,A和B.

  41. A字段有索引,B没有索引。
  42. 这种情况索引必定失效。如果走索引是 索引扫描+全表扫描+合并数据,效率必然低于直接全表扫描。
  43. 如果where中的两个字段都有索引,索引可能走,也可能不走。这个跟数据量和mysql的优化器有关系。

    如果字段类型是字符串,where是一定要加引号

  44. 不加引号数据类型不匹配。是字符串跟数字比较。

  45. mysql会隐式的做类型转换,把数据库字段转浮点类型比较。
  46. 转浮点后,浮点类型没有索引。必然失效。

    like通配符可能导致索引失效

  47. 并不是用了like通配符,索引一定失效。而是like查询以%开头,索引才会失效。

  48. 优化方式

    1. 把%放在后面。
    2. 使用覆盖索引。覆盖索引:即需要查询的数据索引都包含。

      联合索引,非第一列

  49. 最左匹配原则。

    对索引列加函数、运算

  50. B+Tree加了运算就变了

索引字段上使用‘!=’或者<> 这个不是大小,这个就是不等的另一个写法,not in,可能导致索引失效。

  1. 这种非操作相当于排除一个去拿其他所有数据。
  2. 如果走索引,也只是在索引中排除了一个去获取其他所有。
  3. 相当于大量数据要回表。因此不走索引。

    如何处理慢查询

  • 三个方向
    • 索引没有命中
    • load了不需要的数据列
    • 数据量太大
    • 死锁
  1. 首先分析语句,看是否load了额外数据。可能是查询了多余的行最后又抛弃掉了。也可能是load了结果中不需要的列。
  2. 分析语句的执行计划。看看是否有没有命中索引的查询。
  3. 如果没有load额外数据、索引也都命中了,查看返回的数据量是否太大。考虑分表
    1. 纵向分表
      1. 避免数据冗余、符合数据库第三范式
      2. 让数据没那么冗余,提高单表效率。
    2. 横向分表
      1. 根据一列或者多列的值把数据行放到两个或者多列的独立表中。
      2. 查询是可能需要多个表名,union操作。
  4. 死锁,查看sql进程 show processlist, kill thread_id。

    ACID靠什么保证

  5. 原子性 undolog,记录了需要回滚的日志信息,事物回滚时撤销已经执行成功的sql.

  6. 一致性 由其他三个特性加代码控制
  7. 隔离性 MVCC
  8. 持久性 持久性由内存+redolog保证,mysql在修改数据时同时在内存和redolog记录这次操作。宕机时可以在redolog恢复。

    什么是两阶段提交

  9. 阶段一: innoDb redolog写盘,innoDb 事物进入prepare状态。

  10. 阶段二: 如果prepare成功,binlog写盘,如果binlog成功。innoDB 事物进入commit状态。实际上是在redolog上写一个commit记录。

数据库 - 图1

为什么要有两阶段提交

  1. 先写redolog后写binlog
    1. 若干redolog先写完,binlog还没有写。
    2. 这个时候如果mysql进程异常重启
    3. redolog是可以恢复的,但是binlog没有恢复。最终导致数据同步时主从数据不一致。
  2. 先写binlog后写redolog

    1. binlog写完,redoLog没有写完即事物无效。
    2. 恢复后,redolog没有写,不会恢复。
    3. binlog会把数据同步到从,最终导致数据不一致。

      DISTANCT 和 GROUP BY 区别

  3. group by 使用了聚合,所以他只计算了聚合后的数据。

  4. 而distanct 不使用聚合。使用索引区快速遍历数据。
  5. 若重复数据很少,且都有索引的情况下,distanct效率高。
  6. 重复数据很多,group效率高。 可配置 having使用。

    COUNT(1),COUNT(*),COUNT(列名)区别

  7. count(*) 包含所有列名,即统计的行数。不会忽略列名为null场景。

  8. count(1) 忽略了所有列,用1代表行数据,不会忽略列值为null,因为列值不在统计范围内。
  9. count(列名) 统计的是单列不为null数据。当单列有字段为null,忽略不统计。
  • 效率问题
  1. count(*) 一般情况下效率最慢,不建议使用,除非极端场景表仅有一个字段。
  2. 如果定义了主键,count(列名)是比count(1)快的。
  3. 如果count(列名),列名非主键,count(1)比count(列名)快。
  4. 如果就是要统计非主键列不为null数据,那只能使用 count(列名)

    查看索引

  • show index from xxx;

image.png

索引底层原理

  • 底层数据结构

    通过 show index from xxx,可以看到 Index_type 为BTREE,实际上是B+TREE

  • B-TREE特点

    1. 叶节点具有相同的深度,叶节点的指针为空
    2. 所有索引元素不重复。
    3. 节点中的数据索引从左到右递增排列。

image.png

  • B+Tree特点
    1. 非叶子节点不存储data, 只存储索引(冗余)。
    2. 叶子节点包含所有索引字段。
    3. 叶子节点用指针连接,提高区间访问性能。

image.png

myisam存储引擎实现(非聚集索引)

  • 索引文件和数据分离

image.png

InnoDB索引实现(聚集):

  1. 表数据本身就是按照B+TREE组织的一个索引结构的文件
  2. 聚集索引:叶子节点包含了完整的数据记录。
  • 单页数据

image.png

  • 多页数据

image.png

hash索引

  1. 存储数据结构为数组+链表+红黑树
  2. 很多时候,hash索引要比B+TREE效率高
  3. 仅能满足 = in的查找,不支持范围查找。
  4. hash冲突链表过长,转红黑树树高度过高。

索引相关操作

创建表

image.png

查看引擎

image.png

查看索引

image.png

插入数据

image.png

根据主键查询,走索引,非主键查询,不走索引

image.png

创建联合索引

image.png

最左匹配原则,根据b字段查询,走索引,否则不走

image.png

Select * 可能导致索引失效

image.png

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

  1. 聚簇索引
    1. 数据索引和数据放在一块,通过B+Tree组织
    2. 找到了索引,也就找到了数据
    3. 数据的物理存放数据与索引是一致的。—》 只要索引是相邻的,那数据在磁盘上也是相邻的。
    4. 优点
      1. 通过索引可以直接获取数据,查询效率相比非聚集索引要高。
      2. 通过聚集索引范围查找高效,数据本身就是按照索引拍好序的。
      3. 聚集索引非常适合在根据索引字段排序场景,数据本身就是排好序的,不需要回表。
    5. 缺点
      1. 维护索引比较昂贵
  2. 非聚簇索引

    1. 叶子节点不存储数据,存储的数据行地址,或者主键。 —》 找到索引后,需要再去地址找数据。

      什么是事物

  3. 事物是一个不可分割的操作序列

  4. 数据库并发控制的最小单位
  5. 执行结果将使数据库数据从一种一致性状态变迁到另一种一致性状态。
  6. 逻辑上的一组操作,要么都执行,要么都不执行。

    事物的四大特征

    原子性

  7. 事物是最小的执行单元,不允许分割

  8. 原子性确保事物动作要么全部完成,要么全部不执行。

    一致性

  9. 事物执行前后,数据保持一致

  10. 多个事物对同一个数据读取的结果是相同的。

    隔离性

  11. 并发访问数据库时,一个用户的事物不被其他事物干扰。

  12. 各并发事物之间数据库是独立的。
  13. 隔离级别: 读未提交、读已提交、可重复读、可串行化

    持久性

  14. 一个事物别提交后,对数据库数据的改变是持久的。

  15. 即使数据库发生故障也不应该对其有任何影响。

    各种读

    脏读 Drity Read

    一句话总结,一个事物读取的是另一个事物先修改后rollback的数据。

  16. 事物A修改了一份数据,未提交

  17. 事物B读取到了这份数据
  18. 事物A回退了这份数据
  19. 事物B读取到的数据是不正确的,称为脏读

    不可重复读 NonReaptable Read

    一句话总结,由于另一个事物对一份数据的修改或者删除,导致本事物多次读取一份自己未修改的数据,多次读取的结果不一致。

  20. 事物A读取到了一份数据。

  21. 事物B修改了这份数据。
  22. 事物A再次读取这份数据。
  23. 两次读取的结果不一致。

    幻读 Phantom Read

    一句话总结,由于另一个事物的数据插入,导致本事物根据同一条件多次查询的数据返回条数不一样。

  24. 事物A根据条件查询出5条数据。

  25. 事物B插入了符合A查询条件的5条数据。
  26. 事物A根据相同条件查询的数据变为了10条。

    不可重复读与幻读的区别

  27. 不可重复读是多事物执行过程中对单条记录的操作产生的,即多事物delete,update单条记录产生的。通过表锁可以解决。

  28. 幻读是多事物执行过程中对整个表的操作产生的,即多事物insert操作产生的。通过行锁无法解决。需要通过表锁解决。

隔离级别

读未提交-ReadUnCommited

最低的隔离级别,可能导致脏读、幻读、不可重复读。

  1. 从名字看,读未提交,即一个事物可以读取另一个事物未提交的内容。
  2. 如果另个事物回退,就是脏读。
  3. 没有回退之前,就是不可重复读。
  4. 另一个事物增加数据,就是幻读。

image.png

读已提交-ReadCommited

可以防止脏读。不能防止不可重复读和幻读。

  1. 即本事物能读取的是另一个事物已经提交的内容,没有提交的不能读取。
  2. 一个事物要么提交,要么回滚,没提交的不能读取,可以防止脏读。
  3. 不能防止不可重复度,幻读。

    可重复读-ReaptableRead(MySql默认隔离级别)

    可以防止脏读,不可重读读。不能防止幻读。

  4. 一份数据中原始字段有个值为9.

  5. A事物读取到的值为9.
  6. B事物修改该值为10.
  7. 无论B事物是否提交,A事物再次读取,获得的值还是9.
  8. 底层实现为redolog记录了B事物对数据修改的反向操作。
  9. 对同一个字段多次读取的结果是一致的,除非数据是被本事物自己修改的。
  10. 下图纠正 用的不是redolog 是undolog

image.png

串行化-Seralizable

  1. 最高的隔离级别,所有事物串行执行。
  2. 因为是串行执行,所以是有事物之间完全没有干扰。可以防止脏读、不可重复读、幻读。

    四种隔离级别和各种读

    | 隔离级别/读 | 脏读 | 不可重复读 | 幻读 | | —- | —- | —- | —- | | 读未提交 | 不可防止 | 不可防止 | 不可防止 | | 读已提交 | 可防止 | 不可防止 | 不可防止 | | 可重复读 | 可防止 | 可防止 | 不可防止 | | 串行化 | 可防止 | 可防止 | 可防止 |

expalin

样例

image.png

id

  1. 查询的序号,包含一组数字,表示查询中执行select子句或操作表的顺序
  2. id相同,执行顺序从上往下
  3. id不同,id值越大,优先级越高,越先执行

    select_type

    查询类型,主要用于区别普通查询,联合查询,子查询等的复杂查询

  4. simple ——简单的select查询,查询中不包含子查询或者UNION

  5. primary ——查询中若包含任何复杂的子部分,最外层查询被标记
  6. subquery——在select或where列表中包含了子查询
  7. derived——在from列表中包含的子查询被标记为derived(衍生),MySQL会递归执行这些子查询,把结果放到临时表中
  8. union——如果第二个select出现在UNION之后,则被标记为UNION,如果union包含在from子句的子查询中,外层select被标记为derived
  9. union result:UNION 的结果

    table

  10. 输出的行所引用的表

partitions

type

显示联结类型,显示查询使用了何种类型,按照从最佳到最坏类型排序

  1. system:表中仅有一行(=系统表)这是const联结类型的一个特例。
  2. const:表示通过索引一次就找到,const用于比较primary key或者unique索引。因为只匹配一行数据,所以如果将主键置于where列表中,mysql能将该查询转换为一个常量
  3. eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于唯一索引或者主键扫描
  4. ref:非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,可能会找多个符合条件的行,属于查找和扫描的混合体
  5. range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是where语句中出现了between,in等范围的查询。这种范围扫描索引扫描比全表扫描要好,因为它开始于索引的某一个点,而结束另一个点,不用全表扫描
  6. index:index 与all区别为index类型只遍历索引树。通常比all快,因为索引文件比数据文件小很多。
  7. all:遍历全表以找到匹配的行

注意:一般保证查询至少达到range级别,最好能达到ref。

possible_keys

  1. 指出MySQL能使用哪个索引在该表中找到行

    key

  2. 显示MySQL实际决定使用的键(索引)。如果没有选择索引,键是NULL。查询中如果使用覆盖索引,则该索引和查询的select字段重叠。

    key_len

  3. 表示索引中使用的字节数,该列计算查询中使用的索引的长度在不损失精度的情况下,长度越短越好。如果键是NULL,则长度为NULL。该字段显示为索引字段的最大可能长度,并非实际使用长度。

    ref

  4. 显示索引的哪一列被使用了,如果有可能是一个常数,哪些列或常量被用于查询索引列上的值

    rows

  5. 根据表统计信息以及索引选用情况,大致估算出找到所需的记录所需要读取的行数

    filtered

    extra

    包含不适合在其他列中显示,但是十分重要的额外信息
    1、Using filesort:说明mysql会对数据适用一个外部的索引排序。而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成排序操作称为“文件排序”
    2、Using temporary:使用了临时表保存中间结果,mysql在查询结果排序时使用临时表。常见于排序order by和分组查询group by。
    3、Using index:表示相应的select操作用使用覆盖索引,避免访问了表的数据行。如果同时出现using where,表名索引被用来执行索引键值的查找;如果没有同时出现using where,表名索引用来读取数据而非执行查询动作。
    4、Using where :表明使用where过滤
    5、using join buffer:使用了连接缓存
    6、impossible where:where子句的值总是false,不能用来获取任何元组
    7、select tables optimized away:在没有group by子句的情况下,基于索引优化Min、max操作或者对于MyISAM存储引擎优化count(*),不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
    8、distinct:优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作。

buffer Pool

  • buffer Pool是mbatis的内存缓冲区,磁盘操作比内存操作慢很多。因此数据的读写优先从bufferPool中操作。
  • bufferPool默认是128M,底层为数组。

    free链表

    作用: 每个控制块(节点)指向bufferPool中数组下标为空的内存空间,当从磁盘获取数据到缓冲区时,迅速找到为空下标放入页数据。
  1. 每次获取数据,都会从磁盘复制整页数据到bufferPool缓冲区中。
  2. 当该页操作完成后,需要从缓冲区写回到磁盘。
  3. 这个过程涉及一个问题,有些数据写完了,有些数据没写完不回刷,部分数据的回刷导致bufferPool的缓冲区数组不连续。
  4. 为了解决这个问题,就引入了bufferPool。 他是一个链表。
  5. 当一页数据写入完了,即bufferPool中的数组对应空间释放,就加一个控制块,可以理解为链表的一个节点,就执行这个不为空的数组空间。
  6. 当需要从磁盘读取数据到bufferpool中时,就直接通过控制块找到了bufferPool中的空数据空间。

image.png

flush链表

  • free链表的作用是从bufferPool中取出数据是,快速找到bufferPool的空下标位置。
  • flush是当方法update操作时,通过flush链表快速找到磁盘对应的页的位置。
  1. 当读取数据到bufferPool是,每个取出的页,通过free链表找到数组中的位置。
  2. 取出数据到bufferPool中的数组时,通过会将该页数据执行flush链表的一个控制块。同时该控制块执行磁盘中的数据页。
  3. 当执行更新语句时,通过控制块找到磁盘的文件页。

image.png

lru链表

  • bufferpool中存储的是数据的缓存,如果有数据比较多的表发生全表扫描,会导致bufferpool中的数据全部刷新。
  • 而全部扫描一般情况是不需要内存缓冲的。
  • 所以为了防止全表扫描给bufferPool换血,就有了lru链表。
  • 连接分为两部分,热数据区(5/8)和冷数据区(1/8)。
  • 同一页的数据访问如果超过1s,会从冷数据区转移到热数据区。
  • 全表扫描的同页数据访问都在1s内,所以都在冷数据区。
  1. bufferPool中每页有数据的元素对应lru链表中一个控制块。
  2. 访问过的数据会优先放入lru链表的冷数据库。
  3. 同一页数据访问超过1s后转移到热数据区。
  4. 全表扫描的数据访问肯定都在冷数据区。因此只刷新了冷数据区。

image.png

Log

redolog

  1. 直接持久化-不使用redolog
    1. 修改bufferPool里面的页数据—脏页
    2. update语句在磁盘转动的时候找到对应的页。找到对应的行。
    3. 修改
    4. 同页第二条数据修改,在磁盘转动时重新找到该页,修改持久化。
  2. 通过redolog持久化
    1. bufferpool数据修改后写入redolog
    2. redolog默认在事物提交时,写入数据到磁盘

image.png

binlog

  1. mysql的东西,用于记录执行的sql语句,在mysql异常宕机后重启重新执行。

    undolog

  2. 事物依赖,执行sql修改bufferpool中的数据时,记录一条反向语句。用于事物回滚时,回退bufferpool中的数据。

    logfile配置(redolog)

    image.png

  3. innodb_log_file_size 每个logfile大小默认48M.

  4. innodb_log_files_in_group logfile文件个数,如果一个满了,会刷新另一个到磁盘,然后重新写另一个。

image.png

  1. 默认值为1,表示事物提交时,立即将redolog进行持久化。
  2. 0表示事物提交时,不立即对redolog进行持久化,交给后台线程去处理。
  3. 2表示事物提交时,立即将redolog写到缓冲区,并不会直接将redolog进行持久化,这种起情况下,如果数据库挂了,但是操作系统没挂,还可以保证事物持久性。

    redolog和binlog区别

  4. redolog是innDB层面,binlog属于Mysql层吗,即不适用inndb存储引擎,binlog可以达到一致性要求。

  5. redolog是物理日志,记录该数据页更新的内容。binlog是逻辑日志,记录的是语句更新的原始逻辑。
  6. redolog是循环写,日志空间大小固定。即会覆盖。 binlog是追加写,即一份写到一定大小后,会更换下一个文件,不会覆盖。
  7. redolog在异常宕机时使用,binlog在主从复制时使用。

    doblue write buffer

    解决数据页的非完整性写入文件

  8. mysql一页为16kb,而操作系统一页为4kb.

  9. 如果直接从bufferPool写入磁盘。有可能导致写入了4kb,有12kb没有写入。
  10. 直接先将数据写入到double write buffer(磁盘16KB)。再从writebuffer写入到表。
  11. 如果过程中一页部分失败,就不从doublewritebuffer写入,由redolog保证一致性。

image.png

文件

查看文件存储位置

image.png

指定文件存储位置

  1. 在mysql 的 my.ini文件中修改datadir路径

    文件说明

  2. InnoDB是默认的存储引擎,对应的data文件位idb后缀。

  3. myISAM
    1. 不支持事物和外键,访问速度快。
    2. 对于没有事物和完整性要求的表,可以选择myISAM创建。
    3. 每张表对应三个文件
      1. .frm 存储表定义,mysql8.0已经转移到了.sdi文件中。
      2. .MYD文件,存储数据
      3. .MYI文件,存储索引

        存储引擎

        查看所有存储引擎(SHOW ENGINES)

        image.png

查询表的存储引擎(show create table xxx;)

image.png

指定表的存储引擎( INGINE=myisam)

image.png

查询innodb每页存储大小:

image.png

数据库三大范式

字段不可分割

  1. 表中的每一列数据都是都是不可分割的数据项。保持数据原子性。
  2. 就是表中字段不可再分割的意思。
  3. 比如存储user的一张表,user有工作电话、个人手机、家庭电话三个号码,不能存储在一个列,应该分三个列存储。

    设计主键,非主键字段都依赖主键

  4. 在满足第一范式的基础上,实体的每个非主键属性完全依赖与主键属性。

  5. 即通过主键必须能确定的一条数据。
  6. 例如多字段为主键的场景下,通过多主键能确定一条记录,这个就是完全依赖。通过多个主键中的单字段确定一条数据。就是非完全依赖。

    非主键字段不能相互依赖

  7. 非主键字段不相互依赖。

  8. 即每列都与主键直接关联,不能再列之间有依赖传递

    MVCC

    什么是MVCC

  9. multi version concurrent controller 多版本控制系统。

  10. 读取数据的时候通过一种类似快照的方式将数据保存下来。
  11. 这样读锁和写锁就不冲突了。
  12. 不同的事物只会在自己的session看到自己的特定版本的数据,版本链。
  13. MVCC只在RC readcommited 和RR reaptable read 两种隔离级别下工作

    1. read uncommited, 读的是当前最新数据,所有事物在当前版本并行执行,没有版本概念。
    2. serializable,所有语句并行执行,所有事物串行执行,所有事物在当前版串行执行,即所有读取都加锁,没有版本概念。
    3. read commited ,当前事物在自己的版本上读取其他事物在它的版本上已经提交的数据。
    4. reapatable read,当前事物值读取它自己版本上的数据,不读取其他事物的版本数据。

      聚簇索引中两个隐藏列

  14. trx_id: 存储事物id,用来存储某条聚簇索引进行修改时候的事物id.

  15. roll_pointer: 存储上个版本的信息

    1. 每次对聚簇索引进行吸怪的时候,都会把老本版写入undolog中。
    2. roll_pointer就是存了一个指针,指向这个聚簇索引的上个版本位置。用它来获取上个版本的信息。
    3. 插入操作的undolog没有这个属性,因为他没有老版本。

      readView

  16. 事物开始创建readview,readView维护当前活动的事物id. 即未提交的事物id.

  17. 访问数据时,获取该数据的事物id.
  18. 如果该数据的事物id在当前readview的左边,说明该事物已经提交。可以访问。
  19. 如果该数据的事物id在当前readview的右边,或者在当前的readView中,不能访问。 获取roll_pointer获取上个版本的信息。重新对比。
  20. read commit 即在每次读取的时候都重新生成readview. 而read reaptable则是在第一次读的时候生成readview。 之后不再生成。

数据库 - 图31

mysql主从同步原理

logdump线程,IO线程、Sql线程

  1. 主要涉及三个线程,master logdump线程,slave IO线程,SQL线程
  2. 主节点binlog,是主从复制的基础,主节点所有数据变更都记录到binlog。
  3. binlog记录了从数据库服务启动或的所有内容变更。
  4. 主节点logdump线程,当binglog发生变更时,将变更内容发给从节点。
  5. 从节点IO线程接收binlog内容。并将其写入到relay(继电器) log中。
  6. 从节点SQL线程读取relaylog文件内容执行。
  7. 主从节点使用binlog+偏移量来确定文件主从同步的位置。从节点会保存已经接收的偏移量,如果从节点发生宕机。会从从节点position重新发起同步。

数据库 - 图32

全同步复制、半同步复制

  • 问题
  1. 由于mysql默认的复制方式是异步的,即主把binlog发给备机之后,是不关注备机是否同步成功的。
  2. 如果从机同步失败了,刚好主机挂了,从升主之后,日志就丢了。
  • 全同步复制
  1. 写入binlog后强制logdump线程同步到从库。
  2. 当从库io线程和sql线程都执行完成后,返回给主库。
  3. 很显然性能会受到影响。
  • 半同步复制
  1. 当从库日志写完后返回给主库,即只有io线程执行完成。sql线程没有执行。
  2. 相比全同步复制都保证了日志一样。 不同的是sql没执行。

mysql支持哪些锁

数据库 - 图33

mysql锁使用场景

  1. 修改数据库表结构会增加表锁-元数据锁
  2. 更新数据未使用索引,行锁会上升为表锁
  3. 更新数据库使用索引会使用行锁。
  4. select * from update 会使用行锁。

乐观锁

  1. 由程序实现、版本号加时间戳实现。

    悲观锁

    表级锁

  2. 表锁 MySql lanyer ,readlock RR。 write lock.手动加。

  3. 元数据锁 MySql layer 自动加。CRUD加读锁,DDL加写锁。
  4. 意向锁 Inndb内部使用,共享读锁,排他写锁。

    行级锁(Inndb)

  5. 共享读锁 手动加,select ….. in share mode.

  6. 排他写锁 自动加,DML insert/update/delete,select ….. for update.

    表级锁

  7. mysql server层实现,每次操作会锁住整张表,锁定粒度大。发生冲突的概率高。并发度低。

  8. mysql的表锁有两种
    1. 一种是元数据锁(mate data lock,MDL)
    2. 一种是表锁。
  9. 表锁有两种表现形式
    1. 表共享读锁
    2. 表独占写锁

      表级锁的使用

  • 表读锁

    1. session1lock table mylock read; --给mylock加读锁
    2. session1: select * from mylock; --可以查询
    3. session1: select * from tdep; -- 不能访问非锁定表
    4. session2: select * from mylock; --可以查询,没有锁 RR
    5. session2: update mylock set name='x' wehere id = 2; --修改阻塞,自动加行写锁
    6. session1: unlock tables; --释放表锁
    7. session2: Rows matched:1 Changed: 1 warning: 0 --修改执行完成
    8. session1: select * from tdpt; --可以访问
  • 表写锁

    session1: lock table mylock write; --给mylock加写锁
    session1: select * from mylock; --可以查询
    session1: select * from tdep; --不能访问非锁定表
    session1: update mylock set name='y' where id=2; --可以执行
    session2: select * from mylcok; --查询阻塞 WR互斥
    session1: unlock tables; -- 释放表锁
    session2: 4 rows in set --查询执行完成
    session1: select * from tdep; --可以访问
    
  • 元数据读锁 ```sql session1: begin; —开启事物

                  select * from mylock; --加MDL读锁
    

    session2: alter table mylock add f int; —修改阻塞 session1: commit; —事物提交或者rollback 释放读锁 session2: query ok; —修改完成

<a name="WYgMc"></a>
### 元数据锁(MetaDataLOck)

1. 属于表级锁,在Mysql5.5版本引入。
1. 当对一个表左CRUD操作时,默认加MDL读锁。
1. 当要多表结构操作的时候,默认加MDL写锁。
<a name="zyQWc"></a>
### 行级锁(InnoDB实现)

1. 每次操作的时候锁住一行数据。
1. 锁定粒度小,发生重读的概率小,并发度最高。
1. 应用在InnoDB存储引擎中。
1. RecordLock锁,锁定单行记录的锁, RC,RR隔离级别都支持。
1. GapLock锁,间隙锁,锁定索引记录间隙,确保索引记录的间隙不便,RR隔离级别支持。
1. next-key lock锁:行锁和间隙锁组合,同时锁住数据,并且锁住数据前面的gap,RR隔离级别支持。
- [ ] 各种锁和隔离级别事物之间的关系、间隙锁等等等待补充-TMDMYSQL东西真多。

<a name="WStGb"></a>
# Mybatis
<a name="qDdYR"></a>
##  什么是Mybatis?

1. Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。
1. MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
1. 通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。
<a name="b7Pp8"></a>
## Mybaits的优点

1. 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
1. 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
1. 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。
1. 能够与Spring很好的集成;
1. 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
<a name="BjKAE"></a>
## MyBatis框架的缺点

1. SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
1. SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
<a name="GIEMZ"></a>
## MyBatis框架适用场合

1. MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
1. 对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。
<a name="RsoAG"></a>
## MyBatis与Hibernate有哪些不同?

1. Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。
1. Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套sql映射文件,工作量大。
1. Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用hibernate开发可以节省很多代码,提高效率。 
<a name="B5WPu"></a>
## #{}和${}的区别是什么?

1. #{}是预编译处理加了单引号,${}是字符串替换。
1. Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
1. Mybatis在处理${}时,就是把${}替换成变量的值。
1. 使用#{}可以有效的防止SQL注入,提高系统安全性。

<a name="WI30U"></a>
## 哪些时候使用${}

1. order by 一般是根据某个列名排序。 如果使用了 #{}.预编译后列名加了单引号。会找不到。需要使用$.
1. select ..... where xxx like '%${xxx}%' ,如果使用了 #{}. 同样预编译加了单引号,sql报错。需要使用${}.
<a name="OBGAu"></a>
## 当实体类中的属性名和表中的字段名不一样 ,怎么办 ?

- 方式一:通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
```xml
 <select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”>
       select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
    </select>
  • 方式二: 通过来映射字段名和实体类属性名的一一对应的关系。 ```xml

      <!–用id属性来映射主键字段–>
      <id property=”id” column=”order_id”>
    
      <!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性–>
      <result property = “orderno” column =”order_no”/>
      <result property=”price” column=”order_price” />
    

<a name="IqP7r"></a>
## 模糊查询like语句该怎么写?

- 方式一:在Java代码中添加sql通配符。使用#
```xml
 string wildcardname = “%smi%”;
    list<name> names = mapper.selectlike(wildcardname);

    <select id=”selectlike”>
     select * from foo where bar like #{value}
    </select>
  • 方式二:在sql语句中拼接通配符,会引起sql注入 使用$,大括号中不能使用jdbcType

    string wildcardname = “smi”;
      list<name> names = mapper.selectlike(wildcardname);
    
      <select id=”selectlike”>
           select * from foo where bar like "%${value}%"
      </select>
    
  • 方式三: 在sql语句中拼接通配符,使用#,不能使用单引号。 ```xml string wildcardname = “smi”; list names = mapper.selectlike(wildcardname);

<a name="vo7OO"></a>
## Dao接口的工作原理是什么

1. Dao接口即Mapper接口。接口的全类名,就是映射文件中的namespace的值;
1. 接口的方法名,就是映射文件中Mapper的Statement的id值;
1. 接口方法内的参数,就是传递给sql的参数。
1. 在Mybatis中,每一个<select>、<insert>、<update>、<delete>标签,都会被解析为一个MapperStatement对象。
1. Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。
> 举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面<br />id 为 findStudentById 的 MapperStatement。

<a name="ELU9G"></a>
## Mapper接口里面的方法是否能重载

1. Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。
1. Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
<a name="Q9KT6"></a>
## @Mapper和@Respsitory区别

1. @Respostitory和@Controller、@Service类似。源码引用的@Component.是Spring的注解。如果不使用@Mapper直接使用@Respostitory开发,Jdbc需要我们收到实现。
1. @Mapper是mybatis的注解,最用是标识在接口上后,mybatis基于JDK动态代理方式,根据接口全类名,加方法名找到对应的mapper文件中的实现。
1. @MapperScan扫描,指定包后不需要配置@Mapper,自动扫描该包下的所有类。

总结

- 一定要有@Mapper或者MapperScan.
- @Respository可以有,可以没有,可以消除idea依赖注入报红问题。
<a name="xCMLg"></a>
## Mybatis是如何进行分页的?分页插件的原理是什么?

1. Mybatis 使用 RowBounds 对象进行分页,也可以直接编写 sql 实现分页,也可以使用Mybatis 的分页插件。
1. 分页插件的原理:实现 Mybatis 提供的接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql。

举例:select from student,拦截 sql 后重写为:select t. from (select * from student)t limit 0,10
<a name="PHG1E"></a>
## Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?

1. 第一种是使用<resultMap>标签,逐一定义数据库列名和对象属性名之间的映射关系。
1. 第二种是使用sql列的别名功能,将列的别名书写为对象属性名。
1. 有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
<a name="H7E4S"></a>
## 如何执行批量插入?

- 方式一: foreach标签
- 方式二:executortype.batch
```xml
 <insert id=”insertname”>
         insert into names (name) values (#{value})
    </insert>
  list<string> names = new arraylist();
    names.add(“fred”);
    names.add(“barney”);
    names.add(“betty”);
    names.add(“wilma”);

    // 注意这里 executortype.batch
    sqlsession sqlsession = sqlsessionfactory.opensession(executortype.batch);
    try {
     namemapper mapper = sqlsession.getmapper(namemapper.class);
     for (string name : names) {
         mapper.insertname(name);
     }
     sqlsession.commit();
    }catch(Exception e){
     e.printStackTrace();
     sqlSession.rollback(); 
     throw e; 
    }
     finally {
         sqlsession.close();
    }

如何获取自动生成的(主)键值?

  1. insert 方法总是返回一个int值 ,这个值代表的是插入的行数。
  2. 如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中。usegeneratedkeys=”true” ```xml insert into names (name) values (#{name})
```java
    name name = new name();
    name.setname(“fred”);

    int rows = mapper.insertname(name);
    // 完成后,id已经被设置到对象中
    system.out.println(“rows inserted = ” + rows);
    system.out.println(“generated key value = ” + name.getid());

在mapper中如何传递多个参数?

  1. {0},#{1}

  2. 使用@param注解
  3. 封装成map ```xml (1)第一种: //DAO层的函数 Public UserselectUser(String name,String area);
    //对应的xml,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。

(2)第二种: 使用 @param 注解: public interface usermapper { user selectuser(@param(“username”) string username,@param(“hashedpassword”) string hashedpassword); } 然后,就可以在xml像下面这样使用(推荐封装为一个map,作为单个参数传递给mapper):

(3)第三种:多个参数封装成map try{ //映射文件的命名空间.SQL片段的ID,就可以调用对应的映射文件中的SQL //由于我们的参数超过了两个,而方法中只有一个Object参数收集,因此我们使用Map集合来装载我们的参数 Map map = new HashMap(); map.put(“start”, start); map.put(“end”, end); return sqlSession.selectList(“StudentID.pagination”, map); }catch(Exception e){ e.printStackTrace(); sqlSession.rollback(); throw e; } finally{ MybatisUtil.closeSqlSession(); }

<a name="TxrKf"></a>
## Mybatis动态sql有什么用?执行原理?有哪些动态sql?

1. Mybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接sql的功能。
1. Mybatis提供了9种动态sql标签:trim | where | set | foreach | if | choose | when | otherwise | bind。
<a name="NoR3f"></a>
## Mybatis动态标签
<a name="EJR3M"></a>
### if标签
```xml
  <select id="selectByStudentSelective" resultMap="BaseResultMap" parameterType="com.homejim.mybatis.entity.Student">
    select
    <include refid="Base_Column_List" />
    from student
    where 1=1
    <if test="name != null and name !=''">
      and name like concat('%', #{name}, '%')
    </if>
    <if test="sex != null">
      and sex=#{sex}
    </if>
  </select>

choose标签

  • choose when otherwise 标签可以帮我们实现 if else 的逻辑。一个 choose 标签至少有一个 when, 最多一个otherwise。

下面是一个查询的例子。

  <select id="selectByIdOrName" resultMap="BaseResultMap" parameterType="com.homejim.mybatis.entity.Student">
    select
    <include refid="Base_Column_List" />
    from student
    where 1=1
    <choose>
      <when test="studentId != null">
        and student_id=#{studentId}
      </when>
      <when test="name != null and name != ''">
        and name=#{name}
      </when>
      <otherwise>
        and 1=2
      </otherwise>
    </choose>
  </select>

where

  • 如我们在写前面的[在 WHERE 条件中使用 if 标签] SQL 的时候, where 1=1 这个条件我们是不希望存在的。 ```xml
<a name="THSB4"></a>
### trim

1. **prefix** 指的是开头拼接内容
1. **prefixOverrides** 去掉trim内容前面的指定的东西
1. **suffix** sql语句尾部拼接的内容
1. **suffixOverrides** 去掉trim内容后面的指定的东西
> 如下 trim就是中prefix属性给的值就是 如果内容里的if有满足条件的 那么就在sql语句中拼接where 如果没有 那么就不拼接where<br />prefixOverrides 给的是and 意思就是去掉第一个and

```xml
<select id="findUserByNameAndSexAndPhoneMoveTrim" resultType="User">
        select * from user
        <trim prefix="where" prefixOverrides="and">
            <if test="username != null and username != ''">
                and username like concat("%",#{username},"%")
            </if>
            <if test="sex != null and sex != ''">
                and sex = #{sex}
            </if>
            <if test="phone != null and phone!=''">
                and phone = #{phone}
            </if>
        </trim>
    </select>

selectKey

  • 作用: 一些情况下,新增一条数据信息,但其主键(id)是数据库自动在数据库生成(自增),而有些业务逻辑的处理是需要要到这个生成的主键(id)。

对于自增来说,一般使用usegeneratedkeys,不用这种扯淡方式。

  • 参数说明
    • keyProperty:对应的model中的主键的属性名,跟数据库的主键对应
    • order:AFTER 表示 SELECT LAST_INSERT_ID() 在insert执行之后执行,多用与自增主键,BEFORE 表示 SELECT LAST_INSERT_ID() 在insert执行之前执行,这样的话就拿不到主键了,适和非自增。
    • resultType:主键类型
  • 自增主键样例 ```xml select LAST_INSERT_ID() insert into user(code,name,remark,sex) values (#{code},#{name},#{remark},#{sex})

- 非自增主键样例
```java
<insert id="add" parameterType="com.demo.pojo.User">
        <!--通过mybatis框架提供的selectKey标签获得自增产生的ID值-->
        <selectKey resultType="string" order="BEFORE" keyProperty="id">
            select uuid()  
        </selectKey>
        insert into user(id,code,name,remark,sex)
                      values
        (#{id},#{code},#{name},#{remark},#{sex})
    </insert>

sql、include 个人感觉这两货太二了,建议不要使用,可读性太差了

<sql id="select">
SELECT * FROM `body_index`
</sql>

<select id="find" resultType="*" resultMap="*">
        <include refid="select"/>
    </select>

Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?

,加上动态sql的9个标签,其中为sql片段标签,通过标签引入sql片段,为不支持自增的主键生成策略标签。 ## Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复? 1. 不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复; 1. 原因就是namespace+id是作为Map的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。 1. 但是,在以前的Mybatis版本的namespace是可选的,不过新版本的namespace已经是必须的了。 ## 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里? 1. Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。 1. 而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。 ## 一对一、一对多的关联查询 ? 1. 一对一和一对多相同点,都使用resultMap指定返回结果。 1. 不同点 一对一使用 association + javaType ,一对多使用 collection 加ofType ```xml










<!--collection  一对多关联查询 -->  
<select id="getClass2" parameterType="int" resultMap="ClassesResultMap2">  
    select * from class c,teacher t,student s where c.teacher_id=t.t_id and c.c_id=s.class_id and c.c_id=#{id}  
</select>  

<resultMap type="com.lcb.user.Classes" id="ClassesResultMap2">  
    <id property="id" column="c_id"/>  
    <result property="name" column="c_name"/>  
    <association property="teacher" javaType="com.hqp.user.Teacher">  
        <id property="id" column="t_id"/>  
        <result property="name" column="t_name"/>  
    </association>  

    <collection property="student" ofType="com.hqp.user.Student">  
        <id property="id" column="s_id"/>  
        <result property="name" column="s_name"/>  
    </collection>  
</resultMap>  

<a name="m2c9B"></a>
## MyBatis实现一对一有几种方式?具体怎么操作的?

1. 方式一,resultMap + association+javaType
1. 方式二,注解 @One
```java
@Select("select * from account")
    @Results(value = {
            @Result(id = true,property = "id",column = "id"),
            @Result(property = "uid",column = "uid"),
            @Result(property = "money",column = "money"),
            @Result(property = "user",column = "uid",one = @One(select = "com.runze.dao.UserDao.findById",fetchType = FetchType.EAGER))
    })

MyBatis实现一对多有几种方式,怎么操作的?

  1. 方式一, resultmap+collection+oftype
  2. 方式二,注解@Many ```java @Results(value = {
         @Result(id = true,property = "id",column = "id"),
         @Result(property = "accounts",column = "id",many = @Many(select = "com.runze.dao.AccountDao.findById",fetchType = FetchType.LAZY))
    
    })
<a name="FjqbV"></a>
## 立即加载、延时加载
<a name="tFfBb"></a>
### 概念

1. 立即加载: 不管用不用,只要一调用方法,马上发起查询。
1. 延时记载: 在真正的使用数据时才发起查询,不用的时候不查。按需加载(懒加载)
<a name="Wbrtd"></a>
### 使用场景

- 一对多、多对多:通常情况下,我们都是采用延迟加载。
- 多对一、一对一:通常情况下,我们都是采用立即加载。
<a name="urXHg"></a>
### 实现一对一的延迟加载

1. 在assciation中配置select标签属性
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mybatis.dao.IAccountDao">
<!--    定义封装account和user的resultMap-->
    <resultMap id="accountUserMap" type="com.mybatis.domain.Account">
        <id property="id" column="id"></id>
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>
<!--        一对一关系映射,配置封装user的内容
        select 属性指定的内容,查询用户的唯一标识
        column 属性指定的内容,用户根据id查询时,所需要的参数的值
-->
        <association property="user" column="uid" javaType="com.mybatis.domain.User" select="com.mybatis.dao.IUserDao.findById"></association>
    </resultMap>
<!--    查询所有-->
    <select id="findAll" resultMap="accountUserMap">
        select * from account
    </select>
</mapper>
  1. 配置lazyLoadingEnabled,aggressivieLazyLoading
    1. lazyLoadingEnabled: 延迟加载的全局开关,当开启时所有关联对象都会延迟加载,特定的关联查询中可以通过fetchType 联覆盖这个配置。 默认值为false.
    2. aggressiveLazyLoading 调用任何方法都会加载该对象的所有属性,否则每个属性按需加载。默认值false. lazyLoadTriggerMethods ```xml <?xml version=”1.0” encoding=”UTF-8” ?> <!DOCTYPE configuration PUBLIC “-//mybatis.org//DTD Config 3.0//EN” “http://mybatis.org/dtd/mybatis-3-config.dtd">
        <transactionManager type="JDBC"></transactionManager>
      
        <dataSource type="POOLED">
      
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT"/>
            <property name="username" value="root"/>
            <property name="password" value="111111"/>
        </dataSource>
      

<a name="wAFV4"></a>
### 实现一对多的延迟加载
```java
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mybatis.dao.IUserDao">
<!--    定义User的resultMap-->
    <resultMap id="userAccountMap" type="com.mybatis.domain.User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="address" column="address"></result>
        <result property="sex" column="sex"></result>
        <result property="birthday" column="birthday"></result>
        <collection property="accounts" ofType="com.mybatis.domain.Account" select="com.mybatis.dao.IAccountDao.findAccountByUid" column="id"></collection>
    </resultMap>
<!--    查询所有-->
    <select id="findAll" resultMap="userAccountMap">
        select * from users
    </select>
    <select id="findById" parameterType="INT" resultType="com.mybatis.domain.User">
        select * from users where id = #{uid}
    </select>
</mapper>
package com.mybatis.dao;

import com.mybatis.domain.Account;

import java.util.List;

public interface IAccountDao {
    /**
     * 查询所有的Account
     * @return
     */
    List<Account> findAll();

    List<Account> findAccountByUid(Integer uid);

}
<select id="findAccountByUid" resultType="com.mybatis.domain.Account" parameterType="Integer">
        select * from account where uid = #{uid}
</select>

实现原理

  1. 使用CGLIB创建目标对象的代理对象
  2. 当调用目标方法时,会进入拦截器方法,比如调用a.getB().getName()时,实际上拦截器会判断a.getB()。时会由拦截器操作。
  3. 拦截器会在调用方法时执行先保存好的sql,执行完成后给B赋值。
  4. 拦截器调用A.setB之后,B有值了,才会继续调用A.getB().getName()方法。

    Mybatis的一级二级缓存

  • 合理的使用缓存是优化中最常见的方法之一
  • 将从数据库查询出来的数据放入缓存,下次查询不必从数据库直接取。而是直接从缓存中读取。避免频繁操作数据库,减轻数据库压力。

    一级缓存,SqlSession级别的缓存

  1. 默认开启,对一条数据的读取会放入一级缓存。
  2. 一级缓存的作用域是同一个sqlsession,sqlsession的作用就是建立和数据库的会话,我们对数据库表的增删改查都是通过sqlsession去执行指定的sql完成的
  3. 而sqlsession和数据库的连接并不是永久连接的,也一定要杜绝这种永久连接;所以就有了sqlsession的创建和关闭,sqlsession默认执行完一段的sql片段后就会close掉sqlsession,即销毁sqlsession
  4. 而下一次对数据库的操作的又会重新建立会话关系,即建立新的sqlsession,所以这就和前一次的执行sql的sqlsession属于不同的SQL session了,也就这两个sqlsession就不存在一级缓存关系了
  • 一级缓存的错误使用方法

    public CourseDto getCourse(String courseId) {
      log.info("测试mybatis默认开启的一级缓存");
      log.info("第一次查询");
      Course course = courseDao.getSqlSession().selectOne("listCourseByAutoMapping", courseId);
      log.info("第二次查询");
      Course course1 = courseDao.getSqlSession().selectOne("listCourseByAutoMapping", courseId);
      CourseDto courseDto = mapper.map(course, CourseDto.class);
      return courseDto;
    }
    

    image.png
    image.png

    由两张图可以看出上面的代码执行了两次与数据库的会话,因为每次都新建了sqlsession,不在一级缓存的作用域内,而且每次执行的sql也都帮我们打印出来了,所以说程序并没有用到我们的一级缓存;那么如何将一级缓存利用起来呢?这就需要用到我们的spring事务管理;现在的代码如下

  • 正确使用方式

    @Transactional(propagation = Propagation.REQUIRES_NEW)//开启spring的声明事务
    @Override
    public CourseDto getCourse(String courseId) {
      log.info("测试mybatis默认开启的一级缓存");
      log.info("第一次查询");
      Course course = courseDao.getSqlSession().selectOne("listCourseByAutoMapping", courseId);
      log.info("第二次查询");
      Course course1 = courseDao.getSqlSession().selectOne("listCourseByAutoMapping", courseId);
      log.info("第二次执行查询完毕");
      CourseDto courseDto = mapper.map(course, CourseDto.class);
      return courseDto;
    }
    

    image.png

    1. 使用了事物之后,事物内所有的sqlSession是同一个。 也可以理解为只要事物不提交。单个线程内的sqlsession就是同一个。
    2. 具体的实现时通过ThreadLocal实现的。

二级缓存 Mapper级别的缓存

  1. 在mybatis-config.xml文件中的标签配置开启缓存 cacheEnabled

    <!--开启缓存,此时配置的是mybatis的二级缓存-->
    
        <setting name="cacheEnabled" value="true"/>
    
  2. 单单配置这个还是不够的,还需要在我们的mapper的xml文件下开启缓存 cache

    <!--namespace必须加上此标签才会开启二级缓存-->
    <cache />
    
  3. pojo需要实现serilizable接口。

    二级缓存的一些配置参数

    blocking: 是否使用阻塞缓存。默认false.

  4. 如果为true,在同mapper(同namespace+同sql+同参数)查询时,会将这些连接起来作为一个key锁住。

  5. 如果缓存命中,释放锁。
  6. 如果缓存没有命中,去数据库查询后放入缓存。释放锁。
  7. 即开启后可防止同时间大量请求执行同sql.

    flushInterval: 情况缓存时间间隔,默认没有时间间隔

  8. 单位为ms,默认是不设置。也就是没有刷新时间健间隔。

  9. 默认缓存仅在调用sql语句时刷新。

    size(引用条目)缓存条目默认1024

    readOnly: 默认false.

  10. 如果设置为ture,只读的缓存为给调用者返回相同的实例。这样效率高些。

  11. 为默认false时,即需要通过序列化拷贝新对象出来。这样安全。但是慢一些。

    eviction回收策略: 默认LRU

  12. LRU - 移除最长时间不使用的。

  13. FIFO- 先进先出,按照对象进入缓存顺序来移除。
  14. SOFT
  15. WEAK TMD跟软引用和弱引用又有啥关系???这个不用要,后面真的用到了研究。

    一级缓存和二级缓存的区别

  16. 实现方式,一级缓存是默认开启的。二级缓存需要配置 cacheEnabled true和 mapper中配置cache开启

  17. 一级缓存基于sqlSession,session变更或者数据变更都会失效。
  18. 二级缓存基于mappper的namespace,所以就有一个很严重的问题,如果一张表有多个mapper,即有多个namespace,如果开启二级缓存,两个mapper不同sql修改同一个数据相互不感知。

    什么是Mybatis的接口绑定? 有哪些实现方式?

  19. 接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定, 我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置。

  20. 一种是通过注解绑定,就是在接口的方法上面加上 @Select、@Update等注解,里面包含Sql语句来绑定
  21. 一种就是通过xml里面写SQL来绑定, 在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。当Sql语句比较简单时候,用注解绑定, 当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多。

    使用MyBatis的mapper接口调用时有哪些要求?

  22. Mapper接口方法名和mapper.xml中定义的每个sql的id相同;

  23. Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同;
  24. Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;
  25. Mapper.xml文件中的namespace即是mapper接口的类路径。
  • 总结: 方法名-id,参数-parameterType,返回值-resultType,接口全名-namespace.

    Mapper编写有几种方式

    第一种:接口实现类继承SqlSessionDaoSupport:使用此种方法需要编写mapper接口,mapper接口实现类、mapper.xml文件。 (1)在sqlMapConfig.xml中配置mapper.xml的位置

    <mapper resource="mapper.xml文件的地址" />
    <mapper resource="mapper.xml文件的地址" />
    

    (2)定义mapper接口 (3)实现类集成SqlSessionDaoSupport mapper方法中可以this.getSqlSession()进行数据增删改查。 (4)spring 配置

    第二种:使用org.mybatis.spring.mapper.MapperFactoryBean: (1)在sqlMapConfig.xml中配置mapper.xml的位置,如果mapper.xml和mappre接口的名称相同且在同一个目录,这里可以不用配置

    <mapper resource="mapper.xml文件的地址" />
    <mapper resource="mapper.xml文件的地址" />
    

    (2)定义mapper接口: ①mapper.xml中的namespace为mapper接口的地址 ②mapper接口中的方法名和mapper.xml中的定义的statement的id保持一致 ③Spring中定义

    第三种:使用mapper扫描器: (1)mapper.xml文件编写: mapper.xml中的namespace为mapper接口的地址; mapper接口中的方法名和mapper.xml中的定义的statement的id保持一致; 如果将mapper.xml和mapper接口的名称保持一致则不用在sqlMapConfig.xml中进行配置。 (2)定义mapper接口: 注意mapper.xml的文件名和mapper的接口名称保持一致,且放在同一个目录 (3)配置mapper扫描器:

    <property name="basePackage" value="mapper接口包地址"></property>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> 
    

    (4)使用扫描器后从spring容器中获取mapper的实现对象。

简述Mybatis的插件运行原理,以及如何编写一个插件。

  1. Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件
  2. Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法
  3. 具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
  • 插件编写

    实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。

MyBatis的声明周期

  • 所谓的生命周期就是第一个对象应该存活的时间,比如一些对象一次用完后就要关闭,使它们被Java虚拟机(JVM)销毁,以避免继续占用资源,所以我们会根据每一个组件的作用去确定其生命。
  1. SqlSessionFactoryBuilder: SqlSessionFactoryBuilder的作用就是在于创建SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder就失去了作用,所以它只能存在于创建SqlSessionFactory的方法中,而不要让其长期存在。
  2. SqlSessionFactory: SqlSessionFactory可以被认为是一个数据库连接池,它的作用是创建SqlSession接口对象。因为MyBatis的本质就是Java对数据库的操作,所以SqlSessionFactory的生命周期在于于整个MyBatis的应用之中,所以一旦创建了SqlSessionFactory的生命周期就等同于MyBatis的应用周期。

由于SqlSessionFactory是一个对数据库的连接池,所以它占据着数据库的连接资源。如果 创建多个SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据资源的控 制,也会导致连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的qing况。 因此在一般的应用中我们往往希望SqlSessionfactory作为一个单例,让它在应用中补共享。

  1. SqlSession:如果说SqlSessionFactory相当于数据库连接池,那么SqlSession就相当于一个数据库连接(Connection对象),你可以在一个事务里面执行多条SQL,然后通过它的commit、rollback等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给SqlSessionFactory,否则数据库资源就很快被消耗精光,系统应付瘫痪,所以用try…catch…fanally语句来保证其正确关闭。
  2. Mapper:Mapper是一个接口,它由SqlSession所创建,所以它的最大生命周期至多和SqlSession保持一致,尽管它很好用,但是由于SqlSession关闭,它的数据库连接资源也会消失,所以它的生命周期应该小于等于SqlSession的生命周期。Mapper代表是一个请求中的业务处理,所以它应该在一个请求中,一旦处理完了相关的业务,就应该废弃它。

    Redis

    RDB和AOF

    RDB: Redis DataBase

  3. 在执行时间间隔内将内存中的数据集快照写入磁盘

  4. 实际操作过程
    1. fork一个子进程
    2. 先将数据写入临时文件
    3. 临时文件写入成功后,再替换之前的文件
  5. 优点
    1. 整个redis库只包含一个文件,dump.rdb,方便持久化
    2. 容灾性好,方便备份。
    3. 性能最大化,fork子进程来完成写操作,让主进程继续处理命令。所以是IO最大化,使用单独子进程来持久化,主进程不会进行任何IO操作。保证了redis的高性能。
    4. 相对于数据集大时,比AOF的启动效率高。
  6. 缺点

    1. 数据安全性低,RDB是间隔一段时间进行持久化。如果持久化之间redis发生故障,会发生数据丢失。所以RDB适合数据要求不严谨的时候。
    2. 由于RDB是通过fork子进程来完成数据持久化的。因此,如果数据集较大的时候,可能导致服务器停止几百ms.甚至1s。

      AOF: Append Only File

  7. 以日志的形式记录服务器锁处理的每个增加、删除操作。查询操作不会记录。

  8. 只允许追加数据,不允许改写数据。
  9. redis重启会从重启的地方开始读取该文件重新构建数据。
  10. 优点
    1. 数据安全: Redis中提供了3种同步策略。
      1. 每秒同步,异步完成。
      2. 每修改同步, 同步完成
      3. 不同步
    2. 每秒同步也可能有问题,服务器宕机会丢1s的数据。
    3. 通过append方式写文件,即使中途服务器宕机也不会破坏已经存在的内容。可以通过 redis-check-aof 工具解决一致性问题。
    4. AOF机制的write模式,定期对AOF文件进行压缩。以达到压缩目的。
  11. 缺点

    1. AOF文件比RDB文件大,恢复速度慢。
    2. 数据集大的时候,比RDB启动效率低。
    3. 运行效率没有RDB高。

      AOF和RDB对比

  12. AOF比RDB更新频率高。优先使用AOF还原数据。—保证一致性。

  13. AOF比RDB更大也更安全
  14. RDB性能好。
  15. 如果两个都配置了优先加载AOF.

    redis基础

    redis 中的数据类型有哪些

    Redis 有 5 种基础数据结构,分别为:string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)。

    Redis 的列表相当于 Java 语言里面的 LinkedList,注意它是链表而不是数组。这意味着 list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n),这点让人非常意外。 当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。

    Redis 的哈希相当于 Java 语言里面的 HashMap,它是无序字典。内部实现结构上同 Java 的 HashMap 也是一致的,同样的数组 + 链表二维结构。第一维 hash 的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。

    Redis 的集合相当于 Java 语言里面的 HashSet,它内部的键值对是无序的唯一的。它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值NULL。 当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。

    zset 类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。

为什么说redis能够快速执行

i. 绝大部分请求是纯粹的内存操作(非常快速) ii. 采用单线程,避免了不必要的上下文切换和竞争条件 iii. 非阻塞IO - IO多路复用

缓存穿透、缓存击穿、缓存雪崩解决方案

缓存穿透

指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询,可能导致 DB 挂掉。

解决方案:

i. 查询返回的数据为空,仍把这个空结果进行缓存,但过期时间会比较短

ii. 布隆过滤器:将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对 DB 的查询。

缓存击穿

对于设置了过期时间的 key,缓存在某个时间点过期的时候,恰好这时间点对这个 Key 有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把 DB 压垮。

解决方案:

i. 使用互斥锁:当缓存失效时,不立即去 load db,先使用如 Redis 的 setnx 去设置一个互斥锁,当操作成功返回时再进行 load db 的操作并回设缓存,否则重试 get 缓存的方法。

ii 永远不过期:物理不过期,但逻辑过期(后台异步线程去刷新)。

缓存雪崩

设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB, DB 瞬时压力过重雪崩。与缓存击穿的区别:雪崩是很多 key,击穿是某一个key 缓存。

解决方案:

将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

Redis 的集群模式

主从复制

当从数据库启动时,会向主数据库发送sync命令,主数据库接收到sync后开始在后台保存快照rdb,在保存快照期间收到的命令缓存起来,当快照完成时,主数据库会将快照和缓存的命令一块发送给从**。复制初始化结束。 之后,主每收到1个命令就同步发送给从。 当出现断开重连后,2.8之后的版本会将断线期间的命令传给重数据库。增量复制

主从复制是乐观复制,当客户端发送写执行给主,主执行完立即将结果返回客户端,并异步的把命令发送给从,从而不影响性能。也可以设置至少同步给多少个从主才可写。 无硬盘复制:如果硬盘效率低将会影响复制性能,2.8之后可以设置无硬盘复制,repl-diskless-sync yes

哨兵模式

哨兵的作用:

1、监控redis主、从数据库是否正常运行

2、主出现故障自动将从数据库转换为主数据库。

哨兵的核心知识

1、哨兵至少需要 3 个实例,来保证自己的健壮性。

2、哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。

3、对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。

4、配置哨兵监控一个系统时,只需要配置其监控主数据库即可,哨兵会自动发现所有复制该主数据库的从数据库。

Redis分布式锁

使用过Redis分布式锁么,它是什么回事?

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。 如果在setnx之后执行expire之前进程意外crash或者要重启维护了,这个锁就永远得不到释放了,使用set指令把setnx和expire合成一条指令来用

红锁

用Redis中的多个master实例,来获取锁,只有大多数实例获取到了锁,才算是获取成功。具体的红锁算法分为以下五步:

获取当前的时间(单位是毫秒)。 使用相同的key和随机值在N个节点上请求锁。这里获取锁的尝试时间要远远小于锁的超时时间,防止某个masterDown了,我们还在不断的获取锁,而被阻塞过长的时间。 只有在大多数节点上获取到了锁,而且总的获取时间小于锁的超时时间的情况下,认为锁获取成功了。 如果锁获取成功了,锁的超时时间就是最初的锁超时时间进去获取锁的总耗时时间。 如果锁获取失败了,不管是因为获取成功的节点的数目没有过半,还是因为获取锁的耗时超过了锁的释放时间,都会将已经设置了key的master上的key删除。

内存淘汰机制

redis 内存淘汰机制有以下几个:

noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。 allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。 allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。 volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。 volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。 volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。

Redis 和 Mysql 的数据不一致怎么办

采用延时双删策略

第二个删redis。如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。 第一个删redis。如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。

public void use(String key, Object data){
    redis.delKey(key);
    db.updateData(data);
    Thread.sleep(800);
    redis.delKey(key);
}

(1)先淘汰缓存 (2)再写数据库(这两步和原来一样) (3)休眠800ms,再次淘汰缓存 这么做,可以将800ms内所造成的缓存脏数据,再次删除。