不定期更新内容到:https://www.upheart.top/
手写:
SELECT DISTINCT
<select_list>
FROM
<left_table> <join_type>
JOIN <right_table> ON <join_condition>
WHERE
<where_condition>
GROUP BY
<group_by_list>
HAVING
<having_condition>
ORDER BY
<order_by_condition>
LIMIT <limit_number>
机读(MySQL读取顺序)
FROM
<left_table>
ON <join_condition>
<join_type> JOIN <right_table>
WHERE
<where_condition>
GROUP BY
<group_by_list>
HAVING
<having_condition>
SELECT DISTINCT
<select_list>
ORDER BY
<order_by_condition>
LIMIT <limit_number>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OvaIYb1r-1570980107103)(https://cdn.jsdelivr.net/gh/zmdlbr/blog-pic-bed/MySQL/MySQL读取顺序.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-asA0cVzf-1570980107111)(https://cdn.jsdelivr.net/gh/zmdlbr/blog-pic-bed/MySQL/七种JOIN.png)]
MySQL Full Join的实现 因为MySQL不支持FULL JOIN,下面是替代方法
left join + union(可去除重复数据)+ right join
--AB全有
SELECT *
FROM tbl_emp a LEFT JOIN tbl_dept b ON a.deptId = b.id
UNION
SELECT *
FROM tbl_emp a RIGHT JOIN tbl_dept b ON a.deptId = b.id;
----A独有+B独有
--A的独有 + B的独有
SELECT *
FROM tbl_emp a LEFT JOIN tbl_dept b ON a.deptId = b.id
WHERE b.id IS NULL
UNION
SELECT *
FROM tbl_emp a RIGHT JOIN tbl_dept b ON a.deptId = b.id
WHERE a.`deptId` IS NULL;
第一范式(1NF)要求数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值
第二范式要求实体中没一行的所有非主属性都必须完全依赖于主键;即:非主属性必须完全依赖于主键
若存在哪个非主属性依赖于主键中的一部分属性,那么要将发生部分依赖的这一组属性单独新建一个实体,并且在旧实体中用外键与新实体关联,并且新实体与旧实体间是一对多的关系
第三范式要求:每个非主属性都不传递依赖于主键
BC范式则不应存在关键字决定关键字的情况。也就是在关联关系表中,一个表有多个属性构成复合的候选键,主属性直接不应该有互相依赖
悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量
乐观锁一般来说有以下2种方式:
使用数据版本(Version)记录机制实现,使用时间戳(timestamp)
MySQL InnoDB采用的是两阶段锁定协议(two-phase locking protocol)。在事务执行过程中,随时都可以执行锁定,锁只有在执行 COMMIT或者ROLLBACK的时候才会释放,并且所有的锁是在同一时刻被释放。前面描述的锁定都是隐式锁定,InnoDB会根据事务隔离级别在需要的时候自动加锁
另外,InnoDB也支持通过特定的语句进行显示锁定,这些语句不属于SQL规范:
SELECT … LOCK IN SHARE MODE SELECT … FOR UPDATE
InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列,分别保存了这个行的创建时间,一个保存的是行的删除时间
一个行记录数据有多个版本对快照数据,这些快照数据在undo log
中
这里存储的并不是实际的时间值,而是系统版本号(可以理解为事务的ID),每开始一个新的事务,系统版本号就会自动递增,事务开始时刻的系统版本号会作为事务的ID
隔离级别在可重复读和读已提交情况下,有没有可能在在读的时候不用加锁,也能实现可重复读?
MVCC实现了保证可重复读并在读数据的时候不需要加锁操作
INSERT
InnoDB为新插入的每一行保存当前系统版本号作为版本号
SELECT
InnoDB会根据以下两个条件检查每行记录:
a.InnoDB只会查找版本早于当前事务版本的数据行
(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的.
b.行的删除版本要么未定义,要么大于当前事务版本号
这可以确保事务读取到的行,在事务开始之前未被删除.
只有a,b同时满足的记录,才能返回作为查询结果.
DELETE
InnoDB会为删除的每一行保存当前系统的版本号(事务的ID)作为删除标识.
UPDATE
InnoDB执行UPDATE,实际上是新插入了一行记录,并保存其创建时间为当前事务的ID,同时保存当前事务ID到要UPDATE的行的删除时间
操作符<>优化
通常<>操作符无法使用索引,举例如下,查询金额不为100元的订单:
select id from orders where amount != 100;
如果金额为100的订单极少,这种数据分布严重不均的情况下,有可能使用索引
鉴于这种不确定性,采用union聚合搜索结果,改写方法如下:
(select id from orders where amount > 100)
union all
(select id from orders where amount < 100 and amount > 0)
OR优化
在Innodb引擎下or无法使用组合索引,比如:
select id,product_name from orders where mobile_no = '13421800407' or user_id = 100;
OR无法命中mobile_no + user_id的组合索引,可采用union,如下所示:
(select id,product_name from orders where mobile_no = '13421800407')
union
(select id,product_name from orders where user_id = 100);
此时id和product_name字段都有索引,查询才最高效
IN优化
IN适合主表大子表小,EXIST适合主表小子表大,由于查询优化器的不断升级,很多场景这两者性能差不多一样了
尝试改为join查询,举例如下:
select id from orders where user_id in (select id from user where level = 'VIP');
采用JOIN如下所示:
select o.id from orders o left join user u on o.user_id = u.id where u.level = 'VIP';
如果你数据库没有主键,那么count(1) 比count(*) 快
如果有主键作为条件count(),那么count(1) 比count(*) 快
如果表里面只有一个字段,那么还是count(*)最快
count(*) 返回表中所有存在行的总数,包括null
count(1) 返回的是去除null以外的所有行的总数,有默认值的也会被记录
三条经验:
任何情况下select count(*) from table 是最优选择
减少select count(*) from table where condition = ? 这样的查询
杜绝 select count(colunm) from table
字段尽可能用NOT NULL,而不是NULL,除非特殊情况
使用NULL会出现那些稀奇古怪的错误呢?
1、NOT IN、!= 等负向条件查询在有 NULL 值的情况下返回非空行的结果集
执行如下 SQL 语句
SELECT * from t2 where name != ‘张三’
什么都没有返回,另一条数据name为NULL
2、使用 concat 函数拼接时,首先要对各个字段进行非 NULL 判断,否则只要任何一个字段为空都会造成拼接的结果为 NULL
3、当用count函数进行统计时,NULL 列不会计入统计
SELECT count(name) from t2
4、查询空行数据,用 is NULL
SELECT * FROM t2 where name is NULL
5、NULL 列需要更多的存储空间,一般需要一个额外的字节作为判断是否为 NULL 的标志位
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,undo 也会产生 redo 来保证undo log的可靠性
当事务提交之后,undo log并不能立马被删除, 而是放入待清理的链表,由purge线程判断是否由其他事务在使用undo段中表的上一个事务之前的版本信息,决定是否可以清理undo log的日志空间
1.DATETIME的日期范围是1001—9999年,TIMESTAMP的时间范围是1970—2038年
2.DATETIME存储时间与时区无关,TIMESTAMP存储时间与时区有关,显示的值也依赖于时区,在mysql服务器,操作系统以及客户端连接都有时区的设置
3.DATETIME使用8字节的存储空间,TIMESTAMP的存储空间为4字节,因此,TIMESTAMP比DATETIME的空间利用率更高
4.DATETIME的默认值为null,TIMESTAMP的字段默认不为空(not null),默认值为当前时间(CURRENT_TIMESTAMP),如果不做特殊处理,并且update语句中没有指定该列的更新值,则默认更新为当前时间
如果在创建表没有显示申明主键,会怎么办?
如果是这种情况,InnoDB会自动帮你创建一个不可见的、长度为6字节的row_id,而且InnoDB 维护了一个全局的 dictsys.row_id,所以未定义主键的表都共享该row_id,每次插入一条数据,都把全局row_id当成主键id,然后全局row_id加1
该全局row_id在代码实现上使用的是bigint unsigned类型,但实际上只给row_id留了6字节,这种设计就会存在一个问题:如果全局row_id一直涨,一直涨,直到2的48幂次-1时,这个时候再+1,row_id的低48位都为0,结果在插入新一行数据时,拿到的row_id就为0,存在主键冲突的可能性
所以,为了避免这种隐患,每个表都需要定一个主键
索引(Index)是帮助MySQL高效获取数据的数据结构。由此可得到索引的本质是一种数据结构。简单理解为:排好序的快速查找数据结构
适合创建索引的情况:
1、主键自动创建唯一索引
2、频繁作为查询条件的字段应当创建索引
3、查询中与其他表关联的字段,即外键适合创建索引
4、查询中排序的字段适合创建索引,因为排序字段若通过索引去访问将大大提高排序速度
5、查询中统计或者分组的字段适合创建索引
不适合建立索引的情况:
1、频繁更新的字段,经常增删改的表,不适合创建索引
2、WHERE条件里用不到字段不适合创建索引
3、表记录太少的情况无需建立索引
4、如果某个数据列中包含许多重复的内容,为它建立索引就没有太大的实际效果
MyISAM 引擎使用 B+Tree 作为索引结构,叶节点的 data 域存放的是数据记录的地址
MyISAM 中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求 key 是唯一的,而辅助索引的 key 可以重复
InnoDB 的数据文件本身就是索引文件。MyISAM 索引和数据文件是分离的,索引文件仅保存数据记录的地址
数据库以页为存储单元,一个页是8K(8192Byte),一页可以存放N条记录。 页在B+树中分为:数据页和索引页。 B+树的高一般为2-4层,因此查找某一键值的行记录只需2-4次IO,效率较高
唯一索引/非唯一索引
唯一索引:1.唯一索引是在表上一个或者多个字段组合建立的索引,这个或者这些字段的值组合起来在表中不可以重复。
非唯一索引:2.非唯一索引是在表上一个或者多个字段组合建立的索引,这个或者这些字段的值组合起来在表中可以重复,不要求唯一
主键索引(主索引)
3.主键索引(主索引)是唯一索引的特定类型。表中创建主键时自动创建的索引 。一个表只能建立一个主索引
主键索引和唯一索引的区别
主键是一种约束,唯一索引是一种索引,两者在本质上是不同的。
主键创建后一定包含一个唯一性索引,唯一性索引并不一定就是主键。
唯一性索引列允许空值,而主键列不允许为空值。
主键列在创建时,已经默认为空值 + 唯一索引了。
主键可以被其他表引用为外键,而唯一索引不能。
一个表最多只能创建一个主键,但可以创建多个唯一索引
聚集索引(聚簇索引),表中记录的物理顺序与键值的索引顺序相同,一个表只能有一个聚集索引
聚集索引和非聚集索引的根本区别是表中记录的物理顺序和索引的排列顺序是否一致
优点是查询速度快,因为一旦具有第一个索引值的记录被找到,具有连续索引值的记录也一定物理的紧跟其后
缺点是对表进行修改速度较慢,这是为了保持表中的记录的物理顺序与索引的顺序一致,而把记录插入到数据页的相应位置,必须在数据页中进行数据重排,降低了执行速度。在插入新记录时数据文件为了维持 B+Tree 的特性而频繁的分裂调整,十分低效
建议使用聚集索引的场合为:
A.某列包含了小数目的不同值 B.排序和范围查找
其他方面的区别:
1.聚集索引和非聚集索引都采用了 B+树的结构,但非聚集索引的叶子层并不与实际的数据页相重叠,而采用叶子层包含一个指向表中的记录在数据页中的指针的方式,聚集索引的叶节点就是数据节点,而非聚集索引的叶节点仍然是索引节点
2.非聚集索引添加记录时,不会引起数据顺序的重组
看上去聚簇索引的效率明显要低于非聚簇索引, 因为每次使用辅助索引检索都要经过两次B+树查找, 这不是多此一举吗? 聚簇索引的优势在哪?
1.由于行数据和叶子节点存储在一起, 这样主键和行数据是一起被载入内存的, 找到叶子节点就可以立刻将行数据返回了, 如果按照主键 Id 来组织数据, 获得数据更快
2.辅助索引使用主键作为"指针", 而不是使用地址值作为指针的好处是, 减少了当出现行移动或者数据页分裂时,辅助索引的维护工作, InnoDB 在移动行时无须更新辅助索引中的这个"指针"。 也就是说行的位置会随着数据库里数据的修改而发生变化, 使用聚簇索引就可以保证不管这个主键 B+树的节点如何变化, 辅助索引树都不受影响
建议使用非聚集索引的场合为:
a.此列包含了大数目的不同值; b.频繁更新的列
InnoDB 使用的是聚簇索引, 将主键组织到一棵 B+树中, 而行数据就储存在叶子节点上, 若使用"where id = 14"这样的条件查找主键, 则按照 B+树的检索算法即可查找到对应的叶节点, 之后获得行数据。 若对 Name 列进行条件搜索, 则需要两个步骤:
第一步在辅助索引 B+树中检索 Name, 到达其叶子节点获取对应的主键。 第二步使用主键在主索引 B+树种再执行一次 B+树检索操作, 最终到达叶子节点即可获取整行数据
按主键聚集
InnoDB 要求表必须有主键(MyISAM 可以没有),如果没有显式指定,则 MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL 自动为 InnoDB 表生成一个隐含字段作为主键,类型为长整形。 同时,请尽量在 InnoDB 上采用自增字段做表的主键。因为 InnoDB 数据文件本身是一棵B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持 B+Tree 的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择。如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页
InnoDB 的辅助索引 data 域存储相应记录主键的值而不是地址。换句话说,InnoDB 的所有辅助索引都引用主键作为 data 域
索引分类
B+树索引,哈希索引,全文索引,RTree索引
覆盖索引
只需通过辅助索引就能获取要查询的信息,而无需再次通过聚集索引查询具体的记录信息。 由于覆盖索引并不包含整行的记录,因此它的大小远远小于聚集索引。它比较适合做一些统计操作
1.如果索引了多个列,要遵守最佳左前缀法则。指的是查询从索引的最左前列开始 并且 不跳过索引中的列
2.不要在索引上做任何操作(计算、函数、自动/手动类型转换),不然会导致索引失效而转向全表扫描
3.不能继续使用索引中范围条件(bettween、<、>、in等)右边的列
4.尽量使用覆盖索引(只查询索引的列(索引列和查询列一致))
5.索引字段上使用(!= 或者 < >)判断时,会导致索引失效而转向全表扫描
6.索引字段使用like以通配符开头(‘%字符串’)时,会导致索引失效而转向全表扫描
解决like ‘%字符串%’时,索引失效问题的方法?使用覆盖索引可以解决
7.索引字段是字符串,但查询时不加单引号,会导致索引失效而转向全表扫描
8.引字段使用 or 时,会导致索引失效而转向全表扫描
注意避免冗余索引
冗余索引指的是索引的功能相同,能够命中 就肯定能命中 ,那么 就是冗余索引如(name,city )和(name )这两个索引就是冗余索引,能够命中后者的查询肯定是能够命中前者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引
MySQLS.7 版本后,可以通过查询 sys 库的 schema_redundant_indexes
表来查看冗余索引
Read uncommitted 读未提交
当隔离级别设置为Read uncommitted 时,就可能出现脏读
Read committed 读提交
当隔离级别设置为Read committed 时,避免了脏读,但是可能会造成不可重复读
Repeatable read 重复读
Repeatable read避免了不可重复读,但还有可能出现幻读
Serializable 序列化 Serializable 是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d2aAdILc-1570980107118)(https://cdn.jsdelivr.net/gh/zmdlbr/blog-pic-bed/MySQL/isolation.png)]
脏读、幻读、不可重复读
1.脏读: 脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
2.不可重复读: 是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。(即不能读到相同的数据内容)
不可重复读:不可重复读是重复读取了另外一个事务已经提交了的数据,当你在一个事务中读两次,同时别的事务改变了数据并提交了事务,你两次读到的数据就不一致.
可重复读:在你的事务中,读到的数据以你第一次读到的为准,即使别的事务在你读到数据之后进行修改了并提交了,你也不会读到已经提交的数据.
3.幻读: 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
排他锁(Exclusive Lock)
排他锁(Exclusive Lock) , 简称X锁。
若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。这就保证了其他事务在T释放A上的锁之前不能再读取和修改A。
规则1:写一个数据之前加X锁, 事务提交之后释放该X锁
共享锁(Share lock)
共享锁(Share lock) ,简称S锁, 这个锁和之前的排他锁X锁有区别, 主要用于读取数据。
如果一个数据加了X锁, 就没法加S锁,没法再加X锁。
如果一个数据加了S锁, 就可以加S锁,没法再加X锁。
规则1:读一个数据之前加S锁, 读完之后立刻释放该S锁。
规则2:读一个数据之前加S锁, 事务提交之后立刻释放该S锁
读未提交(最低的事务隔离级别)脏数据
现象:不会丢失数据,事务1还未提交的修改能被事务2读取。可以读到没有提交或者回滚的内容 (脏数据)。
原理:写数据时加上X锁,直到事务结束, 读的时候不加锁。
读已提交-不可重复读
现象:能避免“丢失数据”和“脏数据”,事务1能读到其他已提交事务的修改,出现“不可重复读”的问题。
原理:写数据的时候加上X锁, 直到事务结束, 读的时候加上S锁, 读完数据立刻释放。(共享锁规则1)
可重复读-幻读
现象:能避免“丢失数据”和“脏数据”, “不可重复读”三个问题,事务1能读取到其他事务新插入读数据,出现“幻读”的问题。
原理:写数据的时候加上X锁, 直到事务结束, 读数据的时候加S锁, 也是直到事务结束。(共享锁规则2)
RR隔离级别是否解决了幻读
在快照读读情况下,mysql通过mvcc来避免幻读。
在当前读读情况下,mysql通过next-key来避免幻读。
select * from t where a=1;属于快照读
select * from t where a=1 lock in share mode;属于当前读
select * from T where number = 1 for update; 属于当前读
不能把快照读和当前读得到的结果不一样这种情况认为是幻读,这是两种不同的使用。所以我认为mysql的rr级别是解决了幻读的。
根据定义,原子性是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做。即要么转账成功,要么转账失败
Mysql怎么保证原子性的?
利用Innodb的undo log
undo log
名为回滚日志,是实现原子性的关键,当事务回滚时能够撤销所有已经成功执行的sql语句,他需要记录你要回滚的相应日志信息
例如:
undo log
记录了这些回滚需要的信息,当事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子
根据定义,隔离性是指多个事务并发执行的时候,事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰
Mysql怎么保证隔离性的?
利用的是锁和MVCC机制
根据定义,持久性是指事务一旦提交,它对数据库的改变就应该是永久性的
Mysql怎么保证持久性的?
利用Innodb的redo log
正如之前说的,Mysql是先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再刷回磁盘上。如果此时突然宕机,内存中的数据就会丢失
怎么解决这个问题?
简单啊,事务提交前直接把数据写入磁盘就行啊
这么做有什么问题?
于是,决定采用redo log
解决上面的问题。当做数据修改的时候,不仅在内存中操作,还会在redo log
中记录这次操作。当事务提交的时候,会将redo log
日志进行刷盘(redo log
一部分在内存中,一部分在磁盘上)。当数据库宕机重启的时候,会将redo log
中的内容恢复到数据库中,再根据undo log
和binlog
内容决定回滚数据还是提交数据
采用redo log的好处?
其实好处就是将redo log
进行刷盘比对数据页刷盘效率高,具体表现如下
redo log
体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快redo log
是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快根据定义,一致性是指事务执行前后,数据处于一种合法的状态,这种状态是语义上的而不是语法上的
那什么是合法的数据状态呢?
这个状态是满足预定的约束就叫做合法的状态,再通俗一点,这状态是由你自己来定义的。满足这个状态,数据就是一致的,不满足这个状态,数据就是不一致的!
如果无法保证一致性会怎么样?
例一:A账户有200元,转账300元出去,此时A账户余额为-100元。你自然就发现了此时数据是不一致的,为什么呢?因为你定义了一个状态,余额这列必须大于0
例二:A账户200元,转账50元给B账户,A账户的钱扣了,但是B账户因为各种意外,余额并没有增加。你也知道此时数据是不一致的,为什么呢?因为你定义了一个状态,要求A+B的余额必须不变
Mysql怎么保证一致性的?
这个问题分为两个层面来说
从数据库层面,数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。数据库必须要实现AID三大特性,才有可能实现一致性。例如,原子性无法保证,显然一致性也无法保证
但是,如果你在事务里故意写出违反约束的代码,一致性还是无法保证的。例如,你在转账的例子中,你的代码里故意不给B账户加钱,那一致性还是无法保证。因此,还必须从应用层角度考虑
从应用层面,通过代码判断数据库数据是否有效,然后决定回滚还是提交数据!
Mysql支持的复制类型:
1):基于语句的复制:在主服务器上执行的SQL语句,在从服务器上执行同样的语句。MySQL默认采用基于语句的复制,效率比较高.一旦发现没法精确复制时,会自动选着基于行的复制。
2):基于行的复制:把改变的内容复制过去,而不是把命令在从服务器上执行一遍.。从MySQL5.0开始支持。
3):混合类型的复制:默认采用基于语句的复制,一旦发现基于语句的无法精确的复制时,就会采用基于行的复制
主从复制的原理:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VPcB5bAJ-1570980107122)(https://cdn.jsdelivr.net/gh/zmdlbr/blog-pic-bed/MySQL/主从复制原理.jpg)]
影响 MySQL-A 数据库的操作,在数据库执行后,都会写入本地的日志系统 A 中。 假设,实时的将变化了的日志系统中的数据库事件操作,通过网络发给 MYSQL-B。MYSQL-B 收到后,写入本地日志系统 B,然后一条条的将数据库事件在数据库中完成。那么,MYSQL-A 的变化,MYSQL-B 也会变化,这样就是所谓的 MYSQL 的复制
日志系统 A,其实它是 MYSQL 的日志类型中的二进制日志,也就是专门用来保存修改数据库表的所有动作,即 bin log。【注意 MYSQL 会在执行语句之后,释放锁之前,写入二进制日志,确保事务安全】
日志系统 B,并不是二进制日志,由于它是从 MYSQL-A 的二进制日志复制过来的,并不是自己的数据库变化产生的,有点接力的感觉,称为中继日志,即 relay log。可以发现,通过上面的机制,可以保证 MYSQL-A 和 MYSQL-B 的数据库数据一致,但是时间上肯定有延迟,即 MYSQL-B 的数据是滞后的
(1)master 将数据改变记录到二进制日志(binary log)中,也即是配置文件 log-bin 指定的文件(这些记录叫做二进制日志事件, binary log events) Slave 服务器中有一个 I/O线程(I/O Thread)在不停地监听 Master的二进制日志(Binary Log)是否有更新: 如果没有它会睡眠等待 Master 产生新的日志事件;如果有新的日志事件(Log Events), 则会将其拷贝至 Slave 服务器中的中继日志(Relay Log)。
(2).slave 将 master 的二进制日志事件(binary log events)拷贝到它的中继日志(relay log)
(3).slave 重做中继日志中的事件,将 Master 上的改变反映到它自己的数据库中。 , 所以两端的数据是完全一样的
Slave 服务器中有一个 SQL 线程(SQL Thread)从中继日志读取事件, 并重做其中的事件, 从而更新 Slave 的数据, 使其与 Master 中的数据一致。 只要该线程与 I/O 线程保持一致,中继日志通常会位于 OS 的缓存中,所以中继日志的开销很小
主从复制的方式
1.同步复制 主服务器在将更新的数据写入它的二进制日志(Binlog)文件中后,必须等待验证所有的从服务器的更新数据是否已经复制到其中,之后才可以自由处理其它进入的事务处理请求
2.异步复制 主服务器在将更新的数据写入它的二进制日志(Binlog)文件中后,无需等待验证更新数据是否已经复制到从服务器中,就可以自由处理其它进入的事务处理请求。
3.半同步复制 主服务器在将更新的数据写入它的二进制日志(Binlog)文件中后,只需等待验证其中一台从服务器的更新数据是否已经复制到其中,就可以自由处理其它进入的事务处理请求,其他的从服务器不用管
InnoDB一棵B+树可以存放多少行数据?
这个问题的简单回答是:约2千万
InnoDB存储引擎也有自己的最小储存单元—页(Page),一个页的大小是16K
innodb的所有数据文件(后缀为ibd的文件),他的大小始终都是16384(16k)的整数倍
1、InnoDB存储引擎的最小存储单元是页,页可以用于存放数据也可以用于存放键值+指针,在B+树中叶子节点存放数据,非叶子节点存放键值+指针
2、索引组织表通过非叶子节点的二分查找法以及指针确定数据在哪个页中,进而在去数据页中查找到需要的数据
这里我们先假设B+树高为2,即存在一个根节点和若干个叶子节点,那么这棵B+树的存放总记录数为:根节点指针数*单个叶子节点记录行数
我们已经说明单个叶子节点(页)中的记录数=16K/1K=16,(这里假设一行记录的数据大小为1k,实际上现在很多互联网业务数据记录大小通常就是1K左右)
那么现在我们需要计算出非叶子节点能存放多少指针,其实这也很好算,我们假设主键ID为bigint类型,长度为8字节,而指针大小在InnoDB源码中设置为6字节,这样一共14字节,我们一个页中能存放多少这样的单元,其实就代表有多少指针,即16384/14=1170,那么可以算出一棵高度为2的B+树,能存放1170*16=18720条这样的数据记录
根据同样的原理我们可以算出一个高度为3的B+树可以存放:
1170*16=21902400条这样的记录,所以在InnoDB中B+树高度一般为1-3层,它就能满足千万级的数据存储,在查找数据时一次页的查找代表一次IO,所以通过主键索引查询通常只需要1-3次IO操作即可查找到数据
如果lineitem表的数据行数为600多万,B+树高度为3,customer表数据行数只有15万,B+树高度也为3,可以看出尽管数据量差异较大,这两个表树的高度都是3,换句话说这两个表通过索引查询效率并没有太大差异,因为都只需要做3次IO,那么如果有一张表行数是一千万,那么他的B+树高度依旧是3,查询效率仍然不会相差太大
大致可以分为以下十个步骤:
1.当我们请求mysql服务器的时候,MySQL前端会有一个监听,请求到了之后,服务器得到相关的SQL语句,执行之前,还会做权限的判断
2.通过权限之后,SQL就到MySQL内部,他会在查询缓存中,看该SQL有没有执行过,如果有查询过,则把缓存结果返回,说明在MySQL内部,也有一个查询缓存.但是这个查询缓存,默认是不开启的,这个查询缓存,和我们的Hibernate,Mybatis的查询缓存是一样的,因为查询缓存要求SQL和参数都要一样,所以这个命中率是非常低的
3.如果我们没有开启查询缓存,或者缓存中没有找到对应的结果,那么就到了解析器,解析器主要对SQL语法进行解析
4.解析结束后就变成一颗解析树,这个解析树其实在Hibernate里面也是有的,大家回忆一下,在以前做过Hibernate项目的时候,是不是有个一个antlr.jar。这个就是专门做语法解析的工具.因为在Hibernate里面有HQL,它就是通过这个工具转换成SQL的,我们编程语言之所以有很多规范、语法,其实就是为了便于这个解析器解析,这个学过编译原理的应该知道
5.得到解析树之后,不能马上执行,这还需要对这棵树进行预处理,也就是说,这棵树,我没有经过任何优化的树,预处理器会这这棵树进行一些预处理,比如常量放在什么地方,如果有计算的东西,把计算的结果算出来等等
6.预处理完毕之后,此时得到一棵比较规范的树,这棵树就是要拿去马上做执行的树,比起之前的那棵树,这棵得到了一些优化
7.查询优化器,是MySQL里面最关键的东西,我们写任何一条SQL,比如SELECT * FROM USER WHERE USERNAME = toby AND PASSWORD = 1,它会怎么去执行?
它是先执行username = toby还是password = 1?每一条SQL的执行顺序查询优化器就是根据MySQL对数据统计表的一些信息,比如索引,比如表一共有多少数据,MySQL都是有缓存起来的,在真正执行SQL之前,他会根据自己的这些数据,进行一个综合的判定,判断这一次在多种执行方式里面,到底选哪一种执行方式,可能运行的最快.这一步是MySQL性能中,最关键的核心点,也是我们的优化原则
8.这里的查询执行计划,也就是MySQL查询中的执行计划,比如要先执行username = toby还是password = 1
9.这个执行计划会传给查询执行引擎,执行引擎选择存储引擎来执行这一份传过来的计划,到磁盘中的文件中去查询,这个时候重点来了,影响这个查询性能最根本的原因是什么?就是硬盘的机械运动,也就是我们平时熟悉的IO,所以一条查询语句是快还是慢,就是根据这个时间的IO来确定的.那怎么执行IO又是什么来确定的?就是传过来的这一份执行计划
10.如果开了查询缓存,则返回结果给客户端,并且查询缓存也放一份
MyISAM 强调的是性能,查询的速度比 InnoDB 类型更快,但是不提供事务支持,InnoDB 提供事务支持事务
MyISAM 不支持外键,InnoDB 支持外键
MyISAM 只支持表级锁,InnoDB 支持行级锁和表级锁,默认是行级锁,行锁大幅度提高了多用户并发操作的性能,innodb 比较适合于插入和更新操作比较多的情况,而 myisam 则适合用于频繁查询的情况,另外,InnoDB 表的行锁也不是绝对的,如果在执行一个 SQL 语句时,MySQL 不能确定要扫描的范围,InnoDB 表同样会锁全表,例如 update table set num=1 where name like “%aaa%”
MyISAM 支持全文索引, InnoDB 不支持全文索引,innodb 从 mysql5.6 版本开始提供对全文索引的支持
MyISAM:允许没有主键的表存在
InnoDB:如果没有设定主键,就会自动生成一个 6 字节的主键(用户不可见)
MyISAM:select count() from table,MyISAM 只要简单的读出保存好的行数,因为MyISAM 内置了一个计数器,count()时它直接从计数器中读
InnoDB:不保存表的具体行数,也就是说,执行 select count(*) from table 时,InnoDB要扫描一遍整个表来计算有多少行
InnoDB索引和MyISAM索引的区别:
一是主索引的区别,InnoDB的数据文件本身就是索引文件,而MyISAM的索引和数据是分开的
二是辅助索引的区别:InnoDB的辅助索引data域存储相应记录主键的值而不是地址,而MyISAM的辅助索引和主
索引没有多大区别