1.DQL:数据查询语言
负责进行数据查询而不会对数据本身进行修改的语句。保留字select是DQL(也是所有SQL)用得最多的动词,其他DQL常用的保留字有from,where,group by,having和order by。这些DQL保留字常与其他类型的SQL语句一起使用。
2.DML:数据操作语言
负责对数据库对象运行数据访问工作的指令集,以insert、delete、update三种指令为核心,分别代表对表中数据进行增、删、改。
3.DDL:数据定义语言
负责数据结构定义与数据库对象定义的语言,由creats、drop与after三个语法所组成,负责对表结构进行增删改。
4.DCL:数据控制语言
DCL以控制用户的访问权限为主,是一种可对数据访问权进行控制的指令,它可以控制特定用户账户对数据表、查看表、预存程序、用户自定义函数等数据库对象的控制权。由grant 和 revoke两个指令组成。grant为授权语句,对应的revoke是撤销授权语句。
5.TCL:事务处理语言
它的语句能确保被DML语句影响的表的所有行及时得以更新。TPL语句包括begin transaction,commit和rollback。
若一个业务只存在一张表上,数据会出现大量重复,导致冗余。此时可以使用多张表联合查询出结果。
但是连接查询会产生笛卡尔积现象,假设集合A={a,b},集合B={0,1,2},则两个集合的笛卡尔积为{(a,0),(a,1),(a,2),(b,0),(b,1),(b,2)}。可以扩展到多个集合的情况。那么多表查询产生这样的结果并不是我们想要的,那么怎么去除重复的、不想要的记录呢?当然是通过条件过滤(要注意的是,条件过滤并不会减少匹配次数,即不会减少运行效率,只不过显示出来的是满足条件下的连接结果而已)。通常要查询的多个表之间都存在关联关系,那么就通过关联关系去除笛卡尔积。
连接查询分为三种:内连接、外连接、交叉连接。
内连接:[inner] join,从左表中取出每一条记录,去右表中与所有的记录进行匹配,匹配必须是某个条件在左表中与右表中相同,最终才会保留结果,否则不保留。
基本语法:
左表 [inner] join 右表 on 左表.字段 = 右表.字段;
on表示连接条件:条件字段就是代表相同的业务含义(如my_student.c_id和my_class.id),大多数情况下为两张表中的主外键关系。
案例1(非等值连接):找到每个员工的工资等级,要求显示员工名,工资及工资等级。
select e.ename, e.sal, s.grade from emp e on e.sal between s.losal and s.hisal
案例2(等值连接):
select * from t_person inner join t_card on t_person.cardId = c_card.cardId
外连接:outer join,以某张表为主,取出里面的所有记录,然后每条与另外一张表进行连接,不管能不能匹配上条件,最终都会保留。能匹配,正确保留;不能匹配,其它表的字段都置空(null)。
查询的结果集包括SQL语句中左表的所有行,右表中匹配的行。如果左表的某行在右表中没有匹配行,则用空值表示。
外连接分为两种:是以某张表为主,有主表
left join:左外连接(左连接),以左表为主表
right join:右外连接(右连接),以右表为主表
案例:
select * from t_person left join t_card on t_person.cardId = t_card.cardId
左连接,左边表,也就是t_person表为主表。
select * from t_person right join t_card on t_person.cardId = t_card.cardId
右连接,右边表,也就是t_card表为主表。
内连接和外连接的区别:
内连接:假设A表和B表连接,使用内连接时,A是A表和B表能匹配上的记录都查询出来。A,B两表没有主次之分。
外连接:A,B两表中有一张表示主表,有一张是次表,主要查询主表中的数据,当副表中的数据没有和主表中的数据相匹配时,副表自动模拟出null与之匹配。
交叉连接:cross join,从一张表中循环取出每一条记录,每条记录都去另外一张表进行匹配,匹配一定保留(没有条件匹配),而连接本身字段就会增加(保留),最终形成的是被连接的两个表所有数据行的笛卡尔积。
交叉连接不常用,应该尽量避免,存在的价值是保证连接这种结构的完整性。
mysql 的内连接、左连接、右连接有什么区别?
left join (左连接):返回包括左表中的所有记录和右表中连接字段相等的记录。
right join (右连接):返回包括右表中的所有记录和左表中连接字段相等的记录。
inner join (等值连接或者叫内连接):只返回两个表中连接字段相等的行。
举例:
A表
id name
1 小王
2 小李
3 小刘
B表
id A_id job
1 2 老师
2 4 程序员
内连接:(只有2张表匹配的行才能显示)
select a.name,b.job from A a inner join B b on a.id=b.A_id
只能得到一条记录
小李 老师
左连接:(左边的表不加限制)
select a.name,b.job from A a left join B b on a.id=b.A_id
三条记录
小王 null
小李 老师
小刘 null
右连接:(右边的表不加限制)
select a.name,b.job from A a right join B b on a.id=b.A_id
两条记录
小李 老师
null 程序员
子查询即在select中嵌套select
格式:
select
```(select)
from
```(select)
where
```(select)
1.出现在where之后
案例:求高于平均薪资的员工。
第一步:查询平均工资
select avg(sal) from emp
第二步:得到平均工资后再找
select ename, sal from emp where sal > (select avg(sal) from emp)
注意,这里不能直接用select ename, sal from emp where sal > avg(sal),因为where之后不能直接用分组函数。
2.出现在from后
案例:找出每个部门平均薪水的薪资等级
第一步:先找出每个部门的平均薪水
select deptno, avg(sal) from emp group by deptno;
第二步:把第一步求出的表当做新表,和工资等级表连接
select t.*, s.grade from (select deptno,avg(sal) as avgsal from emp group by deptno) t join salgrade s on t.avgsal between s.losal and s.hisal
3.在select后
案例:找每个员工所在部门名称,显示员工名和部门名。
select e.ename (select d.dname from dept id where e.deptno = d.deptno) as dname from emp e
(也可直接select e.ename, d,dname from emp e join dept d on e.deptno = d.deptno)
什么时候使用连接查询?什么时候使用子查询?
答:连接查询查询的是两张表及以上的信息,或者查询的是两个字段及以上的信息,这两个字段不在同一张表内。有时候是两个字段,有时候是两张表,总之是两个,而不是一个。
子查询一般查的是一个字段的信息或一张表里面的信息,只是给的条件并不能直接映射到这张表,需要推出来。当没有给出任何条件时,查询的是两张表综合在一起的信息,并且两张表是主外键相关的。
int 整型(java中的int)
bigint 长整型(Long)
float 浮点型(float,double)
char 定长字符串(String)
varchar 可变长字符串(StringBuffer/StringBuilder)
date 日期类型
BLOB 二进制大对象(存储图片,视频等流媒体信息)
CLOB 字符大对象(存储较大文本)
char 和 varchar 的区别是什么?
float 和 double 的区别是什么?
FLOAT 类型数据占 4 字节,有效数字是8位。
DOUBLE 类型数据占 8字节,有效数字是16位。
不支持事务、也不支持外键,优势是访问速度快,对事务完整性没有要求或者以select,insert为主的应用基本上可以用这个引擎来创建表。
支持3种不同的存储格式,分别是:静态表、动态表、压缩表
MySQL默认引擎,优点是支持事务、行级锁、外键等,可以保证数据的安全。在MySQL数据库崩溃后,提供自动恢复机制。此外,它还支持级联删除和级联更新。
比MyISAM引擎,写的处理效率会差一些,并且会占用更多的磁盘空间以保留数据和索引。
Memory存储引擎不支持事务,使用存在于内存中的内容来创建表。每个memory表只实际对应一个磁盘文件,格式是.frm。memory类型的表访问非常的快(最快的),因为它的数据是放在内存中的,并且默认使用HASH索引,但是一旦服务关闭,表中的数据就会丢失。
主要用于内容变化不频繁的代码表,或者作为统计操作的中间结果表,便于高效地对中间结果进行分析并得到最终的统计结果。
MEMORY存储引擎的表可以选择使用BTREE索引或者HASH索引,两种不同类型的索引有其不同的使用范围。
事务是数据库操作的最小的,不可再分的工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行。
事务(Transaction)是用来维护数据库完整性的,它能够保证一系列的MySQL操作要么全部执行,要么全不执行。
注意,和事务相关的语句只有DML语句(增删改),因为事务机制是为了保证数据的完整性和安全性,而DML语句就是和数据相关的。
(1)A 原子性:事务中的所有操作可以看做一个原子,事务是应用中不可再分的最小的逻辑执行体。要么全部执行,要么全不执行。
(2)C 一致性:事务执行的结果必须使数据库从一个一致性状态,变到另一个一致性状态。
(3)I 隔离性:各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务,都是隔离的。
(4)D 持久性:事务一旦提交,对数据所做的任何改变,都会记录到永久存储器中,是永久性的。
隔离级别 | 导致的问题 |
---|---|
Read-Uncommitted(读未提交) 0 | 导致脏读 |
Read-Committed(读已提交) 1 | 避免脏读,允许不可重复读和幻读 |
Repeatable-Read(可重复读) 2 | MySQL默认的隔离级别。避免不可重复读即脏读,允许幻读 |
Serializable(串行化读) 3 | 串行化读,事务只能一个一个执行,避免了 脏读、不可重复读、幻读。执行效率慢,使用要慎重 |
详细解释:
0 Read-Uncommitted:
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果(即对方事务还未提交,我就可以读到对方的数据了)。
本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
1 Read-Committed:
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经已经提交了的事务所做的改变。这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
2 Repeatable-Read:
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。
不过理论上,这会导致另一个棘手的问题:幻读(Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control 间隙锁)机制解决了该问题。
注:其实多版本只是解决不可重复读问题,而加上间隙锁(也就是它这里所谓的并发控制)才解决了幻读问题。
3 Serializable:
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。这种隔离性能很低,一般很少用。
注:
重复读:事务没结束时,不管我读多少次,都会是原来的数据,不管对方提交几次。直到我此次事务结束。
不可重复读:事务没结束的时候,我读到的每次数据都不一样。
脏读:一个事务对数据进行了增删改查,但是未提交事务。另一个事物可以读取到未提交的数据,如果第一个事务进行了回滚,那么第二个事务就读到了脏数据。
幻读:当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行。
查看MySQL事务隔离级别:select @@tx_isolation;
设置事务的隔离级别:set global transaction isolation level + 隔离级别
索引的本质是一种排好序的数据结构,它可以使对应于表的 SQL 语句执行得更快。索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。
MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型。
1.1 Hash 索引
Hash 索引是比较常见的一种索引,他的单条记录查询的效率很高,时间复杂度为1。但是,Hash索引并不是最常用的数据库索引类型,尤其是常用的 Innodb引擎就是不支持hash索引的。主要原因是Hash索引适合精确查找,但是范围查找不适合
存储引擎都会为每一行计算一个hash码,不同键值行的hash码通常是不一样的,hash索引中存储的就是Hash码,hash 码彼此之间是没有规律的,且 Hash 操作并不能保证顺序性,所以值相近的两个数据,有可能Hash值相差很远,会被分到不同的桶中,这也是为什么不适合范围查找。
比如现在来执行下面的sql语句:
select * from sanguo where name=‘鸡蛋’
可以直接对‘鸡蛋’按哈希算法算出来一个数组下标,然后可以直接从数据中取出数据并拿到所对应那一行数据的地址,进而查询那一行数据, 那么如果现在执行下面的sql语句:
select * from sanguo where name>‘鸡蛋’
则无能为力,因为哈希表的特点就是可以快速的精确查询,但是不支持范围查询。
问:那Hash表在哪些场景比较适合?
答:等值查询的场景,就只有KV(Key,Value)的情况,例如Redis、Memcached等这些NoSQL的中间件。
1.2 二叉树
二叉树是有序的,所以是支持范围查询的。
但是他的时间复杂度是O(log(N)),为了维持这个时间复杂度,更新的时间复杂度也得是O(log(N)),那就得保持这棵树是完全平衡二叉树了。
平衡二叉树用来做索引时,数据量越duo,树就越高,查询的成本就会随着树高的增加而增加。
1.3 B树
同样的元素,B树的表示要比完全平衡二叉树要“矮”,原因在于B树中的一个节点可以存储多个元素。 B树中每个节点中不仅包含数据的 key 值,还有 data 值。
其实B树就是一个不错的数据结构了,可以使用二分查找的查找方式,用来做索引效果还是不错的。
但是,单数每页的存储空间是有限的,如果 data 比较大,会导致每个节点的 key 存储的较少,当数据量较大的时候,同样会导致B树很深,从而增加了磁盘 IO 的次数,进而影响查询效率。
1.4 B+树
Mysql的基本存储结构是页(记录都存在页里边)。
2.1 数据页
MySQL 在存储数据的时候是以数据页为最小单位的,且数据在数据页中的存储是连续的,数据页中的数据是按照主键排序的(没有主键是由 MySQL自己维护的 ROW_ID 来排序),数据页和数据页之间是通过双向链表来关联的,数据与数据时间是通过单向链表来关联的。
2.2 索引页
索引页中记录的是每页数据页的页号和该数据页中最小的主键的记录,也就是说最小主键和数据页号不是单纯的维护在主键目录中了,而是演变成了索引页,索引页和数据页类似,一张不够存就分裂到下一张。
假如现在要查找 id=20 的这条记录,那我应该到哪个索引页中查找该条记录呢?所以这个时候肯定是需要去维护索引页的。
没错,MySQL 也是这么设计的,也就是说 MySQL 同时也设计出了用于维护索引页的数据结构,其实也还叫索引页,只不过他们是在不同的层级,类似下面这样子的:
也就是说维护索引页的索引页是在真正存储记录和数据页的索引页的上一层(比如上面的索引页8),现在如果你想查找 id=20 的这条记录,那就是从最上层的索引页开始查找,通过二分法查找,很快就能够定位到 id=19 这条记录是在索引页 2 上,然后到就索引页 2 上面查找,接着就是和之前一样了(注意,索引页中的记录也是通过单向链表连接的),根据各个最小的主键能够定位到 id=19 是在数据页5上。
2.3 索引页的分层
我们已经知道到索引页太多会往上一层扩散,那现在假设上一层的索引页记录也太多了,那该怎么办?很简单,继续分裂,再往上一层继续。
我们来模拟一个查找的过程,假设你要查找 34 这条记录,说实话我根本不知道这条记录在哪里。好,现在我们就来模拟 MySQL 的查找过程,首先从最顶层的索引页开始查找,因为 id=34,因此定位到了索引页16(因为是有序的),然后到索引页 16 中继续查找,此时同样能够定位到 id=34 在索引页 3 中,然后继续查找,最终能够定位到数据实在数据页 8 中。
简单来说,主键查找的流程为:每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。
B+树,也是二叉搜索树的一种,但是他的数据仅仅存储在叶子节点(在这里就是数据页),像这种索引页+数据页组成的B+树就是聚簇索引
换句话说,聚簇索引是 MySQL 基于主键索引结构创建的。聚簇索引的数据是根据主键的顺序保存。因此适合按主键索引的区间查找,可以有更少的磁盘I/O,加快查询速度。但是也是因为这个原因,聚簇索引的插入顺序最好按照主键单调的顺序插入,否则会频繁的引起页分裂,严重影响性能。
2.4 非主键索引
我们要明白,你建立多少个索引,MySQL 就会帮你维护多少的B+树(这下是不是也突然想明白了为什么索引不能建立太多了?以前就知道不能建立太多索引,因为索引也会占用空间,实际上这就是根本原因)。
假如现在对 name 建立索引,此时 MySQL 会根据 name 维护一个单独的 B+ 树结构,数据依旧存放在数据页中,只不过非主键索引的叶子节点存储的是主键的值。
假设现在执行这样的SQL:
SELECT name FROM student WHERE name='wx'
那这个时候的查询是可以的,用到了索引而且不需要回表。因为查询的字段(即 select 后面的查询字段)仅仅有 name,这个时候是能够直接获取到最终的记录的。
但是如果执行这样的SQL(假设 student 中除了name还有其他的字段):
SELECT * FROM student WHERE name='wx'
那这下子就完蛋了,因为现在虽然根据 name 很快的定位到了该条记录,但是此时的 B+ 树的数据页中存放的仅仅是主键键值,并不会存其他的字段,所以这个时候其他的属性值是获取不到的。这个时候就需要回表了。
MySQL 就会根据定位到的某条记录中的 id 去维护 id 的那个 B+ 树中查找。因为主键索引中数据页记录的是一条记录的完整的记录,这个过程就叫回表。
再强调下回表的含义:根据非主键索引查询到的结果并没有查找的字段值,此时就需要再次根据查找到的主键从主键索引的根节点开始查找,这样再次查找到的记录才是完成的。
那怎么判断是否出现了回表?
只有在使用了索引,且使用了where的情况下,才代表回表查询数据。
那么问题来了,我们知道了主键索引查询只会查一次,而非主键索引需要回表查询多次。那么,非主键索引一定会查询多次吗?其实并不一定,这就用到了覆盖索引。
覆盖索引(covering index)指一个查询语句的执行只用从索引中就能够取得,不必从数据表中读取。也可以称之为实现了索引覆盖。 当一条查询语句符合覆盖索引条件时,MySQL只需要通过索引就可以返回查询所需要的数据,这样避免了查到索引后再返回表操作,减少I/O提高效率。
比如刚刚我们是 select * ,查询所有的,我们如果只查询ID,其实在Name字段的索引上就已经有了,那就不需要回表了。
覆盖索引可以减少树的搜索次数,提升性能,他也是我们在实际开发过程中经常用来优化查询效率的手段。
很多联合索引的建立,就是为了支持覆盖索引,特定的业务能极大的提升效率。
最左匹配原则:
如有索引 (a,b,c,d),查询条件 a=1 and b=2 and c>3 and d=4,则会在每个节点依次命中a、b、c,无法命中d(c已经是范围查询了,d肯定是排不了序了)。
根据最左匹配原则,我们一般把排序分组频率最高的列放在最左边,以此类推。
假如创建一个(a,b)的联合索引,那么它的索引树是这样的:
可以看到a的值是有顺序的,1,1,2,2,3,3,而b的值是没有顺序的1,2,1,4,1,2。所以b = 2这种查询条件没有办法利用索引,因为联合索引首先是按a排序的,b是无序的。
同时我们还可以发现在a值相等的情况下,b值又是按顺序排列的,但是这种顺序是相对的。所以最左匹配原则遇上范围查询就会停止,剩下的字段都无法使用索引。例如a = 1 and b = 2 a,b字段都可以使用索引,因为在a值确定的情况下b是相对有序的,而a>1and b=2,a字段可以匹配上索引,但b值不可以,因为a的值是一个范围,在这个范围中b是无序的。
假如建立联合索引(a,b,c):
select * from table_name where a = '1' and b = '2' and c = '3'
select * from table_name where b = '2' and a = '1' and c = '3'
select * from table_name where c = '3' and b = '2' and a = '1'
这叫全局匹配查询,用到了索引,where子句几个搜索条件顺序调换不影响查询结果,因为Mysql中有查询优化器,会自动优化查询顺序。
select * from table_name where a = '1'
select * from table_name where a = '1' and b = '2'
select * from table_name where a = '1' and b = '2' and c = '3'
匹配左边的列时,都从最左边开始连续匹配,用到了索引。
select * from table_name where b = '2'
select * from table_name where c = '3'
select * from table_name where b = '1' and c = '3'
这些没有从最左边开始,最后查询没有用到索引,用的是全表扫描 。
select * from table_name where a = '1' and c = '3'
如果不连续时,只用到了a列的索引,b列和c列都没有用到。
问题1:常用的索引有哪些种类?
问题2.:索引的目的是什么?索引对数据库系统的负面影响是什么?为数据表建立索引的原则有哪些?什么情况下不宜建立索引?
目的:
快速访问数据表中的特定信息,提高检索速度创建唯一性索引,保证数据库表中每一行数据的唯一性,加速表和表之间的连接。
附: 使用分组和排序子句进行数据检索时,可以显著减少查询中分组和排序的时间。
负面影响:
创建索引和维护索引需要耗费时间,这个时间随着数据量的增加而增加;索引需要占用物理空间,不光是表需要占用数据空间,每个索引也需要占用物理空间;当对表进行增、删、改、的时候索引也要动态维护,这样就降低了数据的维护速度。
原则:
什么情况下不宜建立索引:
问题3:MySQL数据库什么情况下设置了索引但无法使用?
问题4:怎么验证 MySQL 的索引是否满足需求?
使用explain
查看 SQL 是如何执行查询语句的,从而分析你的索引是否满足需求。
explain select * from table where type=1
1.第一范式(1NF):确保每列保持原子性即列不可分。
第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库满足第一范式。
第一范式的合理遵循需要根据系统给的实际需求来确定。比如某些数据库系统中需要用到“地址”这个属性,本来直接将“地址”属性设计成为一个数据库表的字段就行,但是如果系统经常访问“地址”属性中的“城市”部分,那么一定要把“地址”这个属性重新拆分为省份、城市、详细地址等多个部分来进行存储,这样对地址中某一个部分操作的时候将非常方便,这样设计才算满足数据库的第一范式。
2.第二范式(2NF):属性完全依赖于主键(属性都是该对象拥有的)
第二范式在第一范式的基础上更进一层,第二范式需要确保数据库表中每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中。
比如要设计一个订单信息表,因为订单中可能会有多种商品,所以要将订单编号和商品编号作为数据库表的联合主键,如下图:
这里产生一个问题:这个表中是以订单编号和商品编号作为联合主键,这样在该表中商品名称、单位、商品价格等信息不与该表的主键相关,而仅仅是与商品的编号相关,所以在这里违反了第二范式的设计原则。
解决办法是,利用对象之间的关系设计表,确认表的个数。比如把这个订单信息表进行拆分,把商品信息分离到另一个表中,把订单项目表也分离到另一个表中,就非常完美了,如下图:
这样设计,在很大程度上减小了数据库的冗余,如果要获取订单的商品信息,使用商品编号到商品信息表中查询即可。
3.第三范式(3NF):每个属性都要跟主键有直接关系而不是间接关系(不能产生传递依赖)
像:a–>b–>c 属性之间含有这样的关系,是不符合第三范式的。第三范式是对第二范式的细化。
比如在设计一个订单数据表的时候,可以将客户编号作为一个外键和订单表建立相应的关系,而不可以在订单表中添加关于客户其他信息(比如姓名、所属公司)的字段,如下面这两个表所示的设计就是一个满足第三范式的数据库表。
例子:儿子的玩具车与玩具枪和爸爸是间接关系,应该拆为 两个表,通过儿子将两个表关联起来:
拆分:
MyISAM 只支持表锁,InnoDB 支持表锁和行锁,默认为行锁。
1.1 行级锁
行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。
优点是由于锁粒度小,争用率低,并发高(也就是可以大大减少数据库操作的冲突)。
缺点是实现复杂,开销大。加锁慢、容易出现死锁。
行锁分 共享锁 和 排它锁。
共享锁又称:读锁。当一个事务对某几行上读锁时,允许其他事务对这几行进行读操作,但不允许其进行写操作,也不允许其他事务给这几行上排它锁,但允许上读锁。
排它锁又称:写锁。当一个事务对某几个上写锁时,不允许其他事务写,但允许读。更不允许其他事务给这几行上任何锁。包括写锁。
共享锁的写法:lock in share mode
例如: select math from zje where math>60 lock in share mode;
排它锁的写法:for update
例如:select math from zje where math >60 for update;
此外,我们要注意,行锁必须有索引才能实现,否则会自动锁全表,那么就不是行锁了。而且两个事务不能锁同一个索引。insert ,delete , update在事务中都会自动默认加上排它锁。
注意事项:
1.2 表级锁
表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。
优点是开销小,加锁快,不会出现死锁。
缺点是锁的粒度大,发生锁冲突的概率高,并发度低。
注意事项:
innodb什么时候使用行级锁和什么时候使用表级锁?
因为增删改的时候才会上锁。而且行锁必须有索引才能实现。所以我们可以知道,只有在你增删改时匹配的条件字段带有索引时,innodb才会使用行级锁,在你增删改时匹配的条件字段不带有索引时,innodb使用的将是表级锁。因为当你匹配条件字段不带有索引时,数据库会全表查询,所以这需要将整张表加锁,才能保证查询匹配的正确性。
在实际生产环境中,我们往往需要满足多人同时对一张表进行增删改,所以就需要使用行级锁,所以这个时候一定要记住为匹配条件字段加索引。
乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。
悲观锁:每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻止,直到这个锁被释放。数据库的乐观锁需要自己实现,在表里面添加一个 version 字段,每次修改成功值加 1,这样每次修改的时候先对比一下,自己拥有的 version 和数据库现在的 version 是否一致,如果不一致就不修改,这样就实现了乐观锁。
乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。