MySql复习
一 数据类型
数值
字符串
char(5) 定长字符串
varchar(5) 可变长度字符串
日期
timestamp 记录行数据的最后修改事件
二 基本查询
1 聚合函数
avg count sum max min
2 排序
order by
asc
desc
3 分组
group by … having …
分组通常跟聚合函数一起用
4 分页
limit 2
limit 2,5
5 去重
distinct
三 链接查询
1 内连接(交集)
select * from building,house where building.id = house.bid
select * from building inner join house on building.id = house.bid
2 外连接
左
select house.* from house left join owner on house.id = owner.hid where owner.name is null
右
right join on
四 视图
1创建视图
create view my_view as select * from building
五 事务
1 事务概念
在一个业务中,多条sql要么执行成,要么执行失败
2 事务的特性
原子性:组成这个事务的多条sql语句,看做一个整体,不可分割
一致性:要么都成功,要么都失败
隔离性:事务跟事务间的影响程度
持久性:数据的改变是永久的
3 事务在高并发下会产生如下问题
幻读
脏读
不可重复读
4 事务的隔离级别
1.隔离级别造成的问题
脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
不可重复读:事务 A 多次执行同一条查询语句,事务 B 在事务A多次查询的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
提示:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。
2.MySQL事务隔离级别
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read uncommitted) | 是 | 是 | 是 |
读已提交(read committed) | 否 | 是 | 是 |
可重复读(repeatable read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
mysql默认的事务隔离级别为:repeatable-read
设置事务隔离级别语法:
set session transaction isolation level 隔离级别;
eg:
set session transaction isolation level read committed;
3.事务隔离级别案例
a、读未提交
打开一个客户端A,并设置当前事务模式为read uncommitted(未提交读),查询users表的初始值
-- 设置事务隔离级别为:读未提交
set session transaction isolation level read uncommitted;
在客户端一的事务提交之前,打开另一个客户端二,更新表users,更新后不要提交事务
这时,虽然客户端二的事务还没提交,但是客户端一已经可以查询到B已经更新的数据
一旦客户端二的事务因为某种原因回滚,所有的操作都将会被撤销,那客户端一查询到的数据其实就是脏数据
b、读已提交
打开一个客户端一,并设置当前事务模式为read committed(读已提交),查询表users的所有记录:
-- 设置事务隔离级别为:读已提交
set session transaction isolation level read committed;
打开另一个客户端二,更新表users,不要提交事务
这时,客户端二的事务还没提交,客户端一不能查询到二已经更新的数据,解决了脏读问题
客户端二的事务提交
客户端一执行与上一步相同的查询,结果 与上一步不一致,即产生了不可重复读的问题
c、可重复读
打开一个客户端一,并设置当前事务模式为repeatable read,查询表users的所有记录
-- 设置事务隔离级别为:可重复读
set session transaction isolation level repeatable read;
打开另一个客户端二,更新表users并提交
在客户端一查询表users的所有记录,与步骤(1)查询结果一致,没有出现不可重复读的问题
幻读:
在客户端一,接着执行update users money= money- 50 where id = 1,money没有变成1000-50=950,tom的money值用的是步骤(2)中的950来算的,所以是900,数据的一致性倒是没有被破坏。可重复读的隔离级别下使用了MVCC机制,select操作不会更新版本号,是快照读(历史版本);insert、update和delete会更新版本号,是当前读(当前版本)
重新打开客户端二,插入一条新数据后提交
在客户端一查询表users的所有记录,没有查出新增数据,所以没有出现幻读
4、总结
1、事务隔离级别为读提交时,写数据只会锁住相应的行
2、事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。
3、事务隔离级别为串行化时,读写数据都会锁住整张表
4、隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
5 事务实现原理是什么?
-
原子性原理:通过undolog回滚日志来实现的,undo log是一种用于撤销回退的日志, 可以理解为当delete一条记录时, undo log中会记录一条对应的insert记录;当insert一条记录时, undo log中会一条对应的delete记录;当update一条记录时, undo log中记录一条对应相反的update记录,如下:
update user set name = "李四" where id = 1; ---修改之前name=张三
此时undo log会记录一条相反的update语句,如下:
update user set name = "张三" where id = 1;
如果这个修改出现异常,可以使用undo log日志来实现回滚操作,以保证事务的一致性。
这里要需要说明一下,在 MySQL5.6.3 之前的版本中,这个 undo tablespace 是和 system tablespace 系统表空间存放在一起的,也就是没有单独的 undo log 文件,直接存放在 ibdata1 文件里边,在 MySQL5.6.3 之后的版本中,MySQL支持将undo log tablespace单独剥离出来,从8.0.3版本开始,默认undo tablespace的个数从0调整为2,也就是在8.0版本中,独立undo tablespace被默认打开
-
持久性原理:Redo Log(重做日志)记录的是新数据的备份。在事务提交前,只要将Redo Log持久化即可,不需要将数据持久化到磁盘。当系统崩溃时,虽然数据没有持久化,但是Redo Log经持久化。系统可以根据Redo Log的内容刷新到磁盘
六 索引
提供sql的查询效率
常见的索引
唯一索引
主键索引
普通索引
组合索引
mysql索引使用的是B+Tree来存储数据
索引在什么时候失效
1.不要在索引列上做任何操作(计算,函数,类型转换)
2、is null和is not null无法使用索引
3、or多条件查询,索引失效
4、字符串不添加引号会导致索引失效
5、like语句中,以%开头的模糊查询,会导致索引失效,如果不想失效,则使用覆盖索引
7、如果mysq|中使用全表扫描比使用索引快,也会导致索引失效
5、两表关联使用的条件字段中字段的长度、编码不一致会导致索引失效
七 锁
在了解数据库锁之前,首先我们必须要明白加锁的是为了解决什么问题,如果你还不清楚的话,那么从现在开始你就知道了,对数据加锁是为了解决事务的隔离性问题,让事务之间相互不影响,每个事务进行操作的时候都必须先对数据加上一把锁,防止其他事务同时操作数据。如果你想一个人静一静,不被别人打扰,那么请在你的房门上加上一把锁。
分类
表锁
读锁
写锁
lock table 表名 read/write
A | B |
---|---|
lock table building read; | |
能读,不能写 | 能读,不能写 |
lock table building write; | |
能读/能写 | 不能读,不能写 |
行锁
一定要有事务,索引
读锁
写锁
select * from 表名 where id=1 for update
行锁在什么时候升级为表锁
当条件列没有索引时升级为表锁
七 如何合理创建索引
1 建议创建索引
1.主键自动建立唯一索引
2.频繁作为查询的条件的字段应该创建索引
3.查询中与其他表关联的字段,外键关系建立索引
4.频繁更新的字段不适合创建索引(因为每次更新不单单是更新了记录还会更新索引,加重IO负担)
5.查询中排序的字段,排序字段若通过索引去访问将大大提高排序的速度
6.查询中统计或者分组字段
2 不建议创建索引
1.表记录太少
2.经常增删改的表
3.数据重复且分布平均的表字段,因此应该只为经常查询和经常排序的数据列建立索引。
注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果
八 MySQL 的常用引擎都有哪些?
答:MySQL 的常用引擎有 InnoDB、MyISAM、Memory 等,从 MySQL 5.5.5 版本开始 InnoDB 就成为了默认的存储引擎。
九 InnoDB 和 MyISAM 有什么区别?
答:
- InnoDB 支持事务,而 MyISAM 不支持事务
- InnoDB 支持行级锁,MyISAM 不支持行级锁,只支持到表锁;
- InnoDB 支持外键,MyISAM 不支持外键;
- MyISAM 支持 FULLTEXT 类型的全文索引,InnoDB 不支持 FULLTEXT 类型的全文索引,但是 InnoDB 可以使用 sphinx 插件支持全文索引,并且效果更好;
- InnoDB 主键查询性能高于 MyISAM。
十 InnoDB引擎表的主键删掉,是不是就没有主键
答:不是,如果把主键删掉了,那么 InnoDB 会自己生成一个长度为 6 字节的 rowid 作为主键。
十一 唯一索引和普通索引哪个性能更好?
答:唯一索引和普通索引的性能对比分为以下两种情况:
- 对于查询来说两者都是从索引树进行查询,性能几乎没有任何区别;
- 对于更新操作来说,因为唯一索引需要先将数据读取到内存,然后需要判断是否有冲突,因此比普通索引多了判断操作,从而性能就比普通索引性能要低。
B-Tree
磁盘预读:系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。InnoDB存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB存储引擎中默认每个页的大小为16KB(16384/1024),可通过参数innodb_page_size将页的大小设置为4K、8K、16K,在MySQL中可通过如下命令查看页的大小:
mysql> show variables like 'innodb_page_size';
而系统一个磁盘块的存储空间往往没有这么大,因此InnoDB每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小16KB。InnoDB在把磁盘数据读入到内存时会以页为基本单位,一般是页的整数倍,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。
B-Tree结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述B-Tree,首先定义一条记录为一个二元组[key, data] ,key为记录的键值,对应表中的主键值,data为一行记录中除主键外的数据。对于不同的记录,key值互不相同。
每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围为大于35。
模拟查找关键字29的过程:
根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】
比较关键字29在区间(17,35),找到磁盘块1的指针P2。
根据P2指针找到磁盘块3,读入内存。【磁盘I/O操作第2次】
比较关键字29在区间(26,30),找到磁盘块3的指针P2。
根据P2指针找到磁盘块8,读入内存。【磁盘I/O操作第3次】
在磁盘块8中的关键字列表中找到关键字29。
分析上面过程,发现需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率
假如说:每个磁盘块的大小是1k(一个指针+一个数据节点是1k),那么一页就是有16个磁盘块,如果每次加载一页,也有16个指针,而且每个指针指向一个磁盘块(第二层),那么对于一个三层的b-树来说。能存储的节点数就是:16X16X16 = 4096
B+Tree
B+Tree是在B-Tree基础上的一种优化,InnoDB存储引擎就是用B+Tree实现其索引结构。
从上一节中的B-Tree结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度
B+树的特性:
- 所有的叶子结点中包含了全部关键字的信息,非叶子节点只存储键值信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接,所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (而B树的非终节点也包含需要查找的有效信息)
- 所有叶子节点之间都有一个链指针。
- 数据记录都存放在叶子节点中。
由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储3个键值及指针信息,则变成B+Tree后其结构如下图所示:
可能上面例子中只有22条数据记录,看不出B+Tree的优点,下面做一个推算:
InnoDB存储引擎中页的大小为16KB,一般表的主键类型为INT(占用4个字节)或BIGINT(占用8个字节),指针类型也一般为4或8个字节,也就是说一个页(B+Tree中的一个节点)中大概存储16KB/(8B+8B)=1K个键值(因为是估值,为方便计算,这里的K取值为1000。也就是说一个深度为3的B+Tree索引可以维护1000 * 1000 * 50(最后一层每个磁盘块存多少数据节点,假设是50个) = 5千万条记录。深度一般是三到四层
实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree的高度一般都在2-4层。mysql的InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要1~3次磁盘I/O操作。
当然B+Tree的索引结构也是有缺点的,有页分裂和页合并的操作。例如:在13和15之间插入14。这就需要页分裂。
数据库中的B+Tree索引可以分为聚集索引(clustered index)和辅助索引(secondary index)。上面的B+Tree示例图在数据库中的实现即为聚集索引,聚集索引的B+Tree中的叶子节点存放的是整张表的行记录数据。辅助索引与聚集索引的区别在于辅助索引的叶子节点并不包含行记录的全部数据,而是存储相应行数据的聚集索引键,即主键。当通过辅助索引来查询数据时,InnoDB存储引擎会遍历辅助索引找到主键,然后再通过主键在聚集索引中找到完整的行记录数据