你都是会点啥技术(五)— 数据库

写在前面的话:还记得2018年的时候开发的项目上线,经过大概一个月,因为数据量增加,造成项目查询页面的延迟,因为项目使用的群体是固定的,所以当时提出来后并没有着力解决,不过我一直对数据库优化这块耿耿于怀,抽出时间来基于MYSQL学习一下!

1.概念知识补充

数据库系统图:

2.查询优化技术分类

(1.)查询重用:①查询结果重用;②查询计划重用。
(2.)查询重写规则:①基于关系型代数;②视图重写、子查询优化、等价位置重写、条件化简、外联接消除、联接消除、嵌套联接消除;③语义优化。
(3.)查询优化算法(基于代价估算模型):①单表扫描算法(全表扫描、索引扫描、行定位扫描);②两表连接算法(嵌套联接算法、归并连接算法、哈希连接算法);③多表连接算法(贪婪)。
(4.)并行查询优化:利用分解查询SQL,进行并行计算查询,提高查询目的。
(5.)分布式查询优化:利用分布式系统优化查询。

3.数据库调优

数据库调优包括数据库管理系统优化和查询优化,主要目的是使数据库有更高的吞吐量和更高的响应时间。

4.差不多了,实际应用开始走起!

第一招explain

显示了mysql如何使用索引来处理语句以及连接表。可以帮助选择更好的索引和写出更优化的查询语句!我们以select语句为例子
详情请参考:https://www.cnblogs.com/yycc/p/7338894.html

第二招子查询优化
1.子查询合并:

原理:把多次表扫描、多次连接减少为低次表扫描和低次连接。

/*
*优化前
*/SELECT * FROM t_user WHERE zhuhuid < 20 AND (EXISTS (SELECT id FROM t_zhuhu WHERE id<15 AND id=5) OREXISTS (SELECT id FROM t_zhuhu WHERE id<15 AND id=10));
/*
*优化后
*/SELECT * FROM t_user WHERE zhuhuid < 20 AND (EXISTS (SELECT id FROM t_zhuhu WHERE id<15 AND id=5 OR id=10))

2.子查询展开:

原理:将子查询上拉到父查询,实质是把某些子查询重写为等价的多表连接操作,这样有关的的访问路径、连接方法、连接顺序可能被有效使用、使得查询语句的层次尽可能减少。

/*
*优化前
*/
SELECT * FROM t_user a,(SELECT * FROM t_zhuhu WHERE
t_zhuhu.id>10) b WHERE a.zhuhuid<10 AND b.id<20
/*
*优化后
*/
SELECT * FROM t_user a,t_zhuhu b WHERE a.zhuhuid<10AND b.id<20 AND b.id>10

3.聚集子查询消除

原理:利用SQL函数消除子查询的优化技术。

SELECT * FROM t_user a WHERE a.zhuhuid > (SELECT AVG(b.id)FROM t_zhuhu b)

能够做子查询优化格式要求:
1.简单Select查询中的子查询,
2.带有DISTINCT、ORDERBY、LIMIT操作的简单SELECT查询中的子查询。
不能做子查询优化的格式:
1.带有UNION操作
2.带有GROUPBY、HAVING、聚集函数
3.使用ORDERBY中带有LIMIT
4.内表、外表的个数超过MySQL支持的最大表的连接数(mysql最大连接数63)。

第三招:视图重写

定义:视图是数据库中基于表的一种对象,把对表的查询固化,这种固化是视图。
原理:MySQL支持简单视图优化技术,MySQL把视图转为对基表的查询,然后进行类似子查询的优化。但不支持复杂视图(带有GROUP BY、Oreder By 等操作称为复杂视图)优化。

/** 创建视图*/
CREATE VIEW t_view AS SELECT * FROM t_user
/*
* 未使用视图
*/
SELECT a.id FROM (SELECT id,zhuhuid FROM t_user) as a
WHERE a.zhuhuid>10
/*
* 使用视图
*/
SELECT id FROM t_view WHERE zhuhuid>10

第四招:等价谓词重写

原理:把逻辑表达式重写成等价的且效率更高的形式,提高查询执行效率。

1.LIKE规则重写

如果name列上存在索引,则可以利用索引提高查询效率。

/*
* 重写前
*/
name LIKE 'abc'
/*
* 重写后
*/
name = 'abc'

2.BETWEEN-AND规则

如果sno上建立了索引,则可以用索引扫描代替原来BETWEEN-AND谓词限定的全表扫描,从而提高了查询的效率。

/*
* 重写前
*/
sno BETWEEN 10 AND 20
/*
* 重写后
*/
sno >=10 AND sno <=20

3.IN转换OR规则

看实际情况使用,如果age列上存在索引,则转换后查询效率会提高。

/*
* 重写前
*/
age IN (8,12,21)
/*
* 重写后
*/
age=8 OR age=12 OR age=21

4.NOT规则

如果col_1建立了索引,则可以用索引扫描代替原来的全表扫描,从而提高查询的效率

/*
* 重写前
*/
NOT (col_1 != col_2)
/*
* 重写后
*/
col_1 = col_2

5.OR重写并集规则

可以分别利用列sex和age上的索引,进程索引扫描,执行UNION操作获得最终结果。

/*
* 重写前
*/
SELECT * FROM student WHERE (sex='f' and age>15) OR age>18
/*
* 重写后
*/
SELECT * FROM student WHERE sex='f' and age>15
UNION
SELECT * FROM student WHERE age>18

第五招:条件化简
1.把HAVING条件并入WHERE条件,前提是SQL中不存在GROUPBY条件或聚集函数的情况下,才能将HAVING条件与WHERE条件合并。

/*
*例子
*/
SELECT * FROM t1 WHERE a1>1 having a2=2
/*
*等价于
*/
SELECT * FROM t1 WHERE a1>1 and a2=2

2.去除表达式中冗余的括号

减少语法分析时产生的AND和OR树的层次。减少了CPU的消耗

/*
*例子
*/
((a AND b)AND (c AND d))
/*
*化简为
*/
a AND b AND c AND d

3.常量传递

/*
*例子
*/
col_1=col_2 AND col_2=3
/*
*化简为
*/
col_1=3 AND col_2=3

4.消除死码,表达式计算,等式变换,不等式变换,布尔表达式变换

尽量减少数据库去计算,把一些计算进行优化,尽量利用索引,提升数据库查询效率。

第六招.外连接消除、嵌套连接消除、连接消除

连接消除:去掉的是被连接的某个对象。
外连接消除:去掉的事外连接的语义,变形为内连接。
嵌套连接消除:就是消除嵌套的连接层次,把多个层次的连接减少为较少层次的连接,尽量"扁平化"。

第七招.8.语义优化

1.连接消除
2.连接引入
3.谓词引入
4.检测空回答集
5.排序优化
6.唯一性使用

9.非SPJ的优化

1.GROUP BY、ORDER BY优化,尽量利用索引。

物理优化

1.索引

优点:提高少量数据的获取/检索速度
缺点:占用存储空间、多个索引耗费索引的挑选时间、降低写操作的性能,需要实时维护索引、并发情况下索引的维护高度复杂。
不使用索引:数据的重复度高、选择率高于10%、表的数据量少

MYSQL高级教程

记录一下学习过程!

1.触发器

四个必要条件:监视地点(table)、监视事件(insert/update/delete)、触发时间(after/before)、触发事件(insert/update/delete)

触发器使用语法练习

/*
* 声明以;为结尾符
*/
delemiter ;   /*
* 监听table_1表在table_1表有insert事件之后更新table_2
*/
create trigger trigger_1
after
insert
on table_1
for each row
beginupdate table_2 set num=num-1;
end;/*
*new.num获取table_1新插入的num值,相反old.num是获取表删除的num值
* insert 只能用 new
* delete 只能用 old
* update 可用 new/old
*/
create trigger trigger_2
after
insert
on table_1
for each row
beginupdate table_2 set num=num-new.num;
end;/*
*declare 声明变量
*select num into rnum/set new.num = rnum;赋值
*if 条件 then 语句 end if; 判断语句
*/
create trigger trigger_3
before
insert
on table_1
for each row
begindeclare rnum int;select num into rnum from table_2 where id=new.id;if new.num > runm thenset new.num = rnum;end if;   update table_2 set num=num-new.num;end;

2.存储过程

/*
*创建存储过程语法
*/
create procedure procedureName()
begin --sql语句;
ends;/*
*查看已有的procedure
*/
show procedure status;
/*
*调用存储过程语法
*/
call procedureName()/** 给存储过程传参数*/
create procedure p1(width int,height int)
beginselect concat('你的面积是',width * height);if width > height thenselect '你挺胖';elseif width<height thenselect '你挺瘦';elseselect '你挺方';end if;
end;
--调用
CALL p1(4,5)/*
*in 输入 out 输出
*/
create procedure p2(in n int,out total int)
begin
declare num int default 0;set total = 0;while num < n doset num = num + 1;set total = total + num;end while;
end;call p2(100,@total)SELECT @total/*
*inout 输入处理后输出,即是输入又是输出
*/
create procedure p3(inout age int)
beginSELECT CONCAT("20年后你的年龄是:",age+20);
end;set @age =18;call p3(@age)/*
*repeat的用法
*/
--删除存储过程
drop  procedure p4create procedure p4()begin declare num int default 0;declare total int default 0;repeatset num = num +1;set total = total + num;until num >= 100 end repeat;select total;endcall p4() /*
*case的用法
*RAND()随机0.1-1的数
*floor()取整
*/
create procedure p5()
begin
declare a int default 1;
set a = floor(RAND()*5);
case a
WHEN 0  then select "我是0";
WHEN 1  then select "我是1";
WHEN 2  then select "我是2";
else select "我不是0,1,2";
end case;
end;call p5();

3.游标

定义:sql查询多条结果集,利用游标可以一次取出一行
声明:declare 游标名 cursor for sql查询结果集
打开:open 游标名
取值:fetch 游标名 into 值1,值2
关闭:close 游标名

CREATE TABLE t_student ( sid INT PRIMARY KEY, sname VARCHAR ( 20 ), sage INT );INSERT INTO t_student ( sid, sname, sage ) VALUES ( 1, "李一", 10 );
INSERT INTO t_student ( sid, sname, sage ) VALUES ( 2, "李二", 20 );
INSERT INTO t_student ( sid, sname, sage ) VALUES ( 3, "李三", 30 );create procedure p6()
begin
declare row_sname varchar(20);
declare you int default 1;
declare c1 cursor for select sname from t_student;
declare exit handler for not found set you =0;
--如果句柄没有值设置you为0
--exit handler触发后,后面的语句不再执行
--continue handler触发后,后面的语句继续执行
--undo handler触发后,前面的语句撤销 mysql不支持
open c1;
repeat
fetch c1 into row_sname;SELECT row_sname;
until you = 0 end repeat;
close c1;
end;call p6

4.用户权限设置

5.主从复制

配置:https://www.cnblogs.com/liaojie970/p/6198547.html

MySQL数据库事物

1.事物的四种隔离级别

由低到高依次为Read uncommitted(未授权读取、读未提交)、Read committed(授权读取、读提交)、Repeatable read(可重复读取)、Serializable(序列化),这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。

(1)Read uncommitted(未授权读取、读未提交)

1)其他事务读未提交数据,出现脏读;
2)如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。
3)避免了更新丢失,却可能出现脏读。也就是说事务B读取到了事务A未提交的数据。
(读未提交:一个事务写数据时,只允许其他事务对这行数据进行读,所以会出现脏读,事务T1读取T2未提交的数据)

(2)Read committed(授权读取、读提交)

1)允许写事务,所以会出现不可重复读
2)读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
3)该隔离级别避免了脏读,但是却可能出现不可重复读。事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。
(读已提交:读取数据的事务允许其他事务进行操作,避免了脏读,但是会出现不可重复读,事务T1读取数据,T2紧接着更新数据并提交数据,事务T1再次读取数据的时候,和第一次读的不一样。即虚读)

(3)Repeatable read(可重复读取)

1)禁止写事务;
2)读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
3)避免了不可重复读取和脏读,但是有时可能出现幻读。这可以通过“共享读锁”和“排他写锁”实现。
(可重复读:读事务会禁止所有的写事务,但是允许读事务,避免了不可重复读和脏读,但是会出现幻读,即第二次查询数据时会包含第一次查询中未出现的数据)

(4)Serializable(序列化)

1)禁止任何事务,一个一个进行;
2)提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
3)序列化是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读。

基于mysql8.0版本查看,如果是老版本把
select @@global.transaction_isolation;
的transaction换成tx例
SELECT @@global.tx_isolation//查看系统当前隔离级别
mysql> select @@global.transaction_isolation;
+--------------------------------+
| @@global.transaction_isolation |
+--------------------------------+
| REPEATABLE-READ                |
+--------------------------------+
1 row in set (0.00 sec)
//查看当前会话隔离级别
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ         |
+-------------------------+
1 row in set (0.00 sec)
//设置当前会话隔离级别
mysql> set transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)
//设置系统当前隔离级别
mysql> set global transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)
2.事物的四大特性

数据库中事务的四大特性(ACID):原子性、一致性、隔离性、持久性。如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性:

(1)原子性(Atomicity)

原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。

(2)一致性(Consistency)

一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
如:拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

(3)隔离性(Isolation)

隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。

(4)持久性(Durability)

持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
例如:我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。

3.出现的三个问题
(1) 脏读

1.脏读定义:

1)说法1:指在一个事务处理过程里读取了另一个未提交的事务中的数据,读取数据不一致。
2)说法2:指事务A对数据进行增删改操作,但未提交,另一事务B可以读取到未提交的数据。如果事务A这时候回滚了,则第二个事务B读取的即为脏数据。

2.举例:

当一个事务正在多次修改某个数据,而在这个事务中多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。
例如:用户A向用户B转账100元,对应SQL命令如下
  update account set money=money+100 where name=’B’; (此时A通知B)
  update account set money=money - 100 where name=’A’;
当只执行第一条SQL时,A通知B查看账户,B发现确实钱已到账(此时即发生了脏读),而之后无论第二条SQL是否执行,只要该事务不提交,则所有操作都将回滚,那么当B以后再次查看账户时就会发现钱其实并没有转。

(2) 不可重复读

1.不可重复读定义:

1)说法1:是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
  2)说法2:一个事务A中发生了两次读操作,第一次读操作和第二次读操作之间,另一个事务B对数据进行了修改,这时两个事务读取的数据不一致。

2.举例:

例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。

3.不可重复读和脏读的区别:

脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
  在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主。但在另一些情况下就有可能发生问题,例如对于同一个数据A和B依次查询就可能不同,A和B就可能打起来了……

(3)虚读(幻读)

1.幻读定义:

1)说法1:是事务非独立执行时发生的一种现象。
  2)说法2:第一个事务A对一定范围的数据进行批量修改,第二个事务B在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。

2.举例:

例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

3.幻读和不可重复读区别:

都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

4.锁知识
一.概述

数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则。对于任何一种数据库来说都需要有相应的锁定机制,所以MySQL自然也不能例外。MySQL数据库由于其自身架构的特点,存在多种数据存储引擎,每种存储引擎所针对的应用场景特点都不太一样,为了满足各自特定应用场景的需求,每种存储引擎的锁定机制都是为各自所面对的特定场景而优化设计,所以各存储引擎的锁定机制也有较大区别。MySQL各存储引擎使用了三种类型(级别)的锁定机制:表级锁定,行级锁定和页级锁定。

1.表级锁定(table-level)

表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。
当然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并大度大打折扣。
使用表级锁定的主要是MyISAM,MEMORY,CSV等一些非事务性存储引擎。

2.行级锁定(row-level)

行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。
虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁。
使用行级锁定的主要是InnoDB存储引擎。

3.页级锁定(page-level)

页级锁定是MySQL中比较独特的一种锁定级别,在其他数据库管理软件中也并不是太常见。页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁定和行级锁定一样,会发生死锁。
在数据库实现资源锁定的过程中,随着锁定资源颗粒度的减小,锁定相同数据量的数据所需要消耗的内存数量是越来越多的,实现算法也会越来越复杂。不过,随着锁定资源颗粒度的减小,应用程序的访问请求遇到锁等待的可能性也会随之降低,系统整体并发度也随之提升。
使用页级锁定的主要是BerkeleyDB存储引擎。

总的来说,MySQL这3种锁的特性可大致归纳如下:

表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
适用:从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。

二、表级锁定

由于MyISAM存储引擎使用的锁定机制完全是由MySQL提供的表级锁定实现,所以下面我们将以MyISAM存储引擎作为示例存储引擎。

1.MySQL表级锁的锁模式

MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。锁模式的兼容性:
对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;
对MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作;
MyISAM表的读操作与写操作之间,以及写操作之间是串行的。当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。

2.如何加表锁

MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。

3.MyISAM表锁优化建议

对于MyISAM存储引擎,虽然使用表级锁定在锁定实现的过程中比实现行级锁定或者页级锁所带来的附加成本都要小,锁定本身所消耗的资源也是最少。但是由于锁定的颗粒度比较到,所以造成锁定资源的争用情况也会比其他的锁定级别都要多,从而在较大程度上会降低并发处理能力。所以,在优化MyISAM存储引擎锁定问题的时候,最关键的就是如何让其提高并发度。由于锁定级别是不可能改变的了,所以我们首先需要尽可能让锁定的时间变短,然后就是让可能并发进行的操作尽可能的并发。

(1) 查询表级锁争用情况
MySQL内部有两组专门的状态变量记录系统内部锁资源争用情况:

mysql> show status like 'table%';
+----------------------------+---------+
| Variable_name              | Value   |
+----------------------------+---------+
| Table_locks_immediate      | 100     |
| Table_locks_waited         | 11      |
+----------------------------+---------+

这里有两个状态变量记录MySQL内部表级锁定的情况,两个变量说明如下:
Table_locks_immediate:产生表级锁定的次数;
Table_locks_waited:出现表级锁定争用而发生等待的次数;
两个状态值都是从系统启动后开始记录,出现一次对应的事件则数量加1。如果这里的Table_locks_waited状态值比较高,那么说明系统中表级锁定争用现象比较严重,就需要进一步分析为什么会有较多的锁定资源争用了。

(2)缩短锁定时间

如何让锁定时间尽可能的短呢?唯一的办法就是让我们的Query执行时间尽可能的短。
a)尽两减少大的复杂Query,将复杂Query分拆成几个小的Query分布进行;
b)尽可能的建立足够高效的索引,让数据检索更迅速;
c)尽量让MyISAM存储引擎的表只存放必要的信息,控制字段类型;
d)利用合适的机会优化MyISAM表数据文件。

(3)分离能并行的操作

说到MyISAM的表锁,而且是读写互相阻塞的表锁,可能有些人会认为在MyISAM存储引擎的表上就只能是完全的串行化,没办法再并行了。大家不要忘记了,MyISAM的存储引擎还有一个非常有用的特性,那就是ConcurrentInsert(并发插入)的特性。
MyISAM存储引擎有一个控制是否打开Concurrent Insert功能的参数选项:concurrent_insert,可以设置为0,1或者2。三个值的具体说明如下:
concurrent_insert=2,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录;
concurrent_insert=1,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置;
concurrent_insert=0,不允许并发插入。
可以利用MyISAM存储引擎的并发插入特性,来解决应用中对同一表查询和插入的锁争用。例如,将concurrent_insert系统变量设为2,总是允许并发插入;同时,通过定期在系统空闲时段执行OPTIMIZE TABLE语句来整理空间碎片,收回因删除记录而产生的中间空洞。

(4)合理利用读写优先级

MyISAM存储引擎的是读写互相阻塞的,那么,一个进程请求某个MyISAM表的读锁,同时另一个进程也请求同一表的写锁,MySQL如何处理呢?
答案是写进程先获得锁。不仅如此,即使读请求先到锁等待队列,写请求后到,写锁也会插到读锁请求之前。
这是因为MySQL的表级锁定对于读和写是有不同优先级设定的,默认情况下是写优先级要大于读优先级。
所以,如果我们可以根据各自系统环境的差异决定读与写的优先级:
通过执行命令SET LOW_PRIORITY_UPDATES=1,使该连接读比写的优先级高。如果我们的系统是一个以读为主,可以设置此参数,如果以写为主,则不用设置;
通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。
虽然上面方法都是要么更新优先,要么查询优先的方法,但还是可以用其来解决查询相对重要的应用(如用户登录系统)中,读锁等待严重的问题。
另外,MySQL也提供了一种折中的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级降低,给读进程一定获得锁的机会。
这里还要强调一点:一些需要长时间运行的查询操作,也会使写进程“饿死”,因此,应用中应尽量避免出现长时间运行的查询操作,不要总想用一条SELECT语句来解决问题,因为这种看似巧妙的SQL语句,往往比较复杂,执行时间较长,在可能的情况下可以通过使用中间表等措施对SQL语句做一定的“分解”,使每一步查询都能在较短时间完成,从而减少锁冲突。如果复杂查询不可避免,应尽量安排在数据库空闲时段执行,比如一些定期统计可以安排在夜间执行。

三、行级锁定

行级锁定不是MySQL自己实现的锁定方式,而是由其他存储引擎自己所实现的,如广为大家所知的InnoDB存储引擎,以及MySQL的分布式存储引擎NDBCluster等都是实现了行级锁定。考虑到行级锁定君由各个存储引擎自行实现,而且具体实现也各有差别,而InnoDB是目前事务型存储引擎中使用最为广泛的存储引擎,所以这里我们就主要分析一下InnoDB的锁定特性。

1.InnoDB锁定模式及实现机制

考虑到行级锁定君由各个存储引擎自行实现,而且具体实现也各有差别,而InnoDB是目前事务型存储引擎中使用最为广泛的存储引擎,所以这里我们就主要分析一下InnoDB的锁定特性。
总的来说,InnoDB的锁定机制和Oracle数据库有不少相似之处。InnoDB的行级锁定同样分为两种类型,共享锁和排他锁,而在锁定机制的实现过程中为了让行级锁定和表级锁定共存,InnoDB也同样使用了意向锁(表级锁定)的概念,也就有了意向共享锁和意向排他锁这两种。
当一个事务需要给自己需要的某个资源加锁的时候,如果遇到一个共享锁正锁定着自己需要的资源的时候,自己可以再加一个共享锁,不过不能加排他锁。但是,如果遇到自己需要锁定的资源已经被一个排他锁占有之后,则只能等待该锁定释放资源之后自己才能获取锁定资源并添加自己的锁定。而意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁。如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。所以,可以说InnoDB的锁定模式实际上可以分为四种:共享锁(S),排他锁(X),意向共享锁(IS)和意向排他锁(IX),我们可以通过以下表格来总结上面这四种所的共存逻辑关系:
如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。
意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。

共享锁(S)
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
排他锁(X)
SELECT * FROM table_name WHERE ... FOR UPDATE

用SELECT … IN SHARE MODE获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。
但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT… FOR UPDATE方式获得排他锁。

2.InnoDB行锁实现方式

InnoDB行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁
在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。下面通过一些实际例子来加以说明。
(1)在不通过索引条件查询的时候,InnoDB确实使用的是表锁,而不是行锁。
(2)由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。
(3)当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。
(4)即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。

3.间隙锁(Next-Key锁)

当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;
对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。
例:
假如emp表中只有101条记录,其empid的值分别是 1,2,…,100,101,下面的SQL:

mysql> select * from emp where empid > 100 for update;

是一个范围条件的检索,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。
InnoDB使用间隙锁的目的:
(1)防止幻读,以满足相关隔离级别的要求。对于上面的例子,要是不使用间隙锁,如果其他事务插入了empid大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;
(2)为了满足其恢复和复制的需要。
很显然,在使用范围条件检索并锁定记录时,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害。
除了间隙锁给InnoDB带来性能的负面影响之外,通过索引实现锁定的方式还存在其他几个较大的性能隐患:
(1)当Query无法利用索引的时候,InnoDB会放弃使用行级别锁定而改用表级别的锁定,造成并发性能的降低;
(2)当Query使用的索引并不包含所有过滤条件的时候,数据检索使用到的索引键所只想的数据可能有部分并不属于该Query的结果集的行列,但是也会被锁定,因为间隙锁锁定的是一个范围,而不是具体的索引键;
(3)当Query在使用索引定位数据的时候,如果使用的索引键一样但访问的数据行不同的时候(索引只是过滤条件的一部分),一样会被锁定。
因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。
还要特别说明的是,InnoDB除了通过范围条件加锁时使用间隙锁外,如果使用相等条件请求给一个不存在的记录加锁,InnoDB也会使用间隙锁。

4.死锁

上文讲过,MyISAM表锁是deadlock free的,这是因为MyISAM总是一次获得所需的全部锁,要么全部满足,要么等待,因此不会出现死锁。但在InnoDB中,除单个SQL组成的事务外,锁是逐步获得的,当两个事务都需要获得对方持有的排他锁才能继续完成事务,这种循环锁等待就是典型的死锁。
在InnoDB的事务管理和锁定机制中,有专门检测死锁的机制,会在系统中产生死锁之后的很短时间内就检测到该死锁的存在。当InnoDB检测到系统中产生了死锁之后,InnoDB会通过相应的判断来选这产生死锁的两个事务中较小的事务来回滚,而让另外一个较大的事务成功完成。
那InnoDB是以什么来为标准判定事务的大小的呢?MySQL官方手册中也提到了这个问题,实际上在InnoDB发现死锁之后,会计算出两个事务各自插入、更新或者删除的数据量来判定两个事务的大小。也就是说哪个事务所改变的记录条数越多,在死锁中就越不会被回滚掉。
但是有一点需要注意的就是,当产生死锁的场景中涉及到不止InnoDB存储引擎的时候,InnoDB是没办法检测到该死锁的,这时候就只能通过锁定超时限制参数InnoDB_lock_wait_timeout来解决。
需要说明的是,这个参数并不是只用来解决死锁问题,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。
通常来说,死锁都是应用设计的问题,通过调整业务流程、数据库对象设计、事务大小,以及访问数据库的SQL语句,绝大部分死锁都可以避免。下面就通过实例来介绍几种避免死锁的常用方法:
(1)在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会。
(2)在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能。
(3)在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁,更新时再申请排他锁,因为当用户申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁。
(4)在REPEATABLE-READ隔离级别下,如果两个线程同时对相同条件记录用SELECT…FOR UPDATE加排他锁,在没有符合该条件记录情况下,两个线程都会加锁成功。程序发现记录尚不存在,就试图插入一条新记录,如果两个线程都这么做,就会出现死锁。这种情况下,将隔离级别改成READ COMMITTED,就可避免问题。
(5)当隔离级别为READ COMMITTED时,如果两个线程都先执行SELECT…FOR UPDATE,判断是否存在符合条件的记录,如果没有,就插入记录。此时,只有一个线程能插入成功,另一个线程会出现锁等待,当第1个线程提交后,第2个线程会因主键重出错,但虽然这个线程出错了,却会获得一个排他锁。这时如果有第3个线程又来申请排他锁,也会出现死锁。对于这种情况,可以直接做插入操作,然后再捕获主键重异常,或者在遇到主键重错误时,总是执行ROLLBACK释放获得的排他锁。

5.什么时候使用表锁

对于InnoDB表,在绝大部分情况下都应该使用行级锁,因为事务和行锁往往是我们之所以选择InnoDB表的理由。但在个别特殊事务中,也可以考虑使用表级锁:
(1)事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。
(2)事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。
当然,应用中这两种事务不能太多,否则,就应该考虑使用MyISAM表了。
在InnoDB下,使用表锁要注意以下两点。
(1)使用LOCK TABLES虽然可以给InnoDB加表级锁,但必须说明的是,表锁不是由InnoDB存储引擎层管理的,而是由其上一层──MySQL Server负责的,仅当autocommit=0、InnoDB_table_locks=1(默认设置)时,InnoDB层才能知道MySQL加的表锁,MySQL Server也才能感知InnoDB加的行锁,这种情况下,InnoDB才能自动识别涉及表级锁的死锁,否则,InnoDB将无法自动检测并处理这种死锁。
(2)在用 LOCK TABLES对InnoDB表加锁时要注意,要将AUTOCOMMIT设为0,否则MySQL不会给表加锁;事务结束前,不要用UNLOCK TABLES释放表锁,因为UNLOCK TABLES会隐含地提交事务;COMMIT或ROLLBACK并不能释放用LOCK TABLES加的表级锁,必须用UNLOCK TABLES释放表锁。正确的方式见如下语句:
例如,如果需要写表t1并从表t读,可以按如下做:

SET AUTOCOMMIT=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
[do something with tables t1 and t2 here];
COMMIT;
UNLOCK TABLES;
6.InnoDB行锁优化建议

InnoDB存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量较高的时候,InnoDB的整体性能和MyISAM相比就会有比较明显的优势了。但是,InnoDB的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让InnoDB的整体性能表现不仅不能比MyISAM高,甚至可能会更差。
(1)要想合理利用InnoDB的行级锁定,做到扬长避短,我们必须做好以下工作:
a)尽可能让所有的数据检索都通过索引来完成,从而避免InnoDB因为无法通过索引键加锁而升级为表级锁定;
b)合理设计索引,让InnoDB在索引键上面加锁的时候尽可能准确,尽可能的缩小锁定范围,避免造成不必要的锁定而影响其他Query的执行;
c)尽可能减少基于范围的数据检索过滤条件,避免因为间隙锁带来的负面影响而锁定了不该锁定的记录;
d)尽量控制事务的大小,减少锁定的资源量和锁定时间长度;
e)在业务环境允许的情况下,尽量使用较低级别的事务隔离,以减少MySQL因为实现事务隔离级别所带来的附加成本。
(2)由于InnoDB的行级锁定和事务性,所以肯定会产生死锁,下面是一些比较常用的减少死锁产生概率的小建议:
a)类似业务模块中,尽可能按照相同的访问顺序来访问,防止产生死锁;
b)在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
c)对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率。
(3)可以通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况:


mysql> show status like 'InnoDB_row_lock%';
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| InnoDB_row_lock_current_waits | 0     |
| InnoDB_row_lock_time          | 0     |
| InnoDB_row_lock_time_avg      | 0     |
| InnoDB_row_lock_time_max      | 0     |
| InnoDB_row_lock_waits         | 0     |
+-------------------------------+-------+

InnoDB 的行级锁定状态变量不仅记录了锁定等待次数,还记录了锁定总时长,每次平均时长,以及最大时长,此外还有一个非累积状态量显示了当前正在等待锁定的等待数量。对各个状态量的说明如下:
InnoDB_row_lock_current_waits:当前正在等待锁定的数量;
InnoDB_row_lock_time:从系统启动到现在锁定总时间长度;
InnoDB_row_lock_time_avg:每次等待所花平均时间;
InnoDB_row_lock_time_max:从系统启动到现在等待最常的一次所花的时间;
InnoDB_row_lock_waits:系统启动后到现在总共等待的次数;
对于这5个状态变量,比较重要的主要是InnoDB_row_lock_time_avg(等待平均时长),InnoDB_row_lock_waits(等待总次数)以及InnoDB_row_lock_time(等待总时长)这三项。尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手指定优化计划。
如果发现锁争用比较严重,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高,还可以通过设置InnoDB Monitors 来进一步观察发生锁冲突的表、数据行等,并分析锁争用的原因。

锁冲突的表、数据行等,并分析锁争用的原因。具体方法如下:

mysql> create table InnoDB_monitor(a INT) engine=InnoDB;

然后就可以用下面的语句来进行查看:

mysql> show engine InnoDB status;

监视器可以通过发出下列语句来停止查看:

mysql> drop table InnoDB_monitor;

设置监视器后,会有详细的当前锁等待的信息,包括表名、锁类型、锁定记录的情况等,便于进行进一步的分析和问题的确定。可能会有读者朋友问为什么要先创建一个叫InnoDB_monitor的表呢?因为创建该表实际上就是告诉InnoDB我们开始要监控他的细节状态了,然后InnoDB就会将比较详细的事务以及锁定信息记录进入MySQL的errorlog中,以便我们后面做进一步分析使用。打开监视器以后,默认情况下每15秒会向日志中记录监控的内容,如果长时间打开会导致.err文件变得非常的巨大,所以用户在确认问题原因之后,要记得删除监控表以关闭监视器,或者通过使用“–console”选项来启动服务器以关闭写日志文件。

引用:
mysql数据库的锁有多少种,怎么编写加锁的sql语句
https://www.cnblogs.com/sessionbest/articles/8689071.htm
数据库四大特性和事务隔离级别
https://www.cnblogs.com/Andya/p/7426436.html

你都是会点啥技术(五)--- 数据库相关推荐

  1. 合肥工业大学机器人技术五十六题

    合肥工业大学机器人技术五十六题 题目要求 //拿球后行为,利用已有 Worldmodel(21) (1)在 playOn 模式下,拿到球以后朝前方快速带球. (2)在 PlayOn 模式下,拿到球以后 ...

  2. 你都是会点啥技术(四)--- Java

    你都是会点啥技术(四)- Java 写在前面的话:到2019年6月份为止,真正使用java有两年了,在大学期间老师教过一遍java(学的不好),看过两三个老师讲解的java视频,每次学习都会有新的体验 ...

  3. 广域网宽带接入技术五EPON技术

    广域网宽带接入技术五EPON技术 3.2.2.ONU测距和时延补偿 3.2.3.以太网OAM工作流程 3.2.4.带宽分配 4.EPON设备基本配置 4.1.OLT常用配置 4.1.1.OLT配置 3 ...

  4. 阿里技术五面(刚拿Offer) 一面+主管二面+总监三四面+HR五面

    刚拿到阿里offer,经历了5次面试,其中4轮技术面,1轮HR面试.在这里分享一下自己的面试经验和学习心得.希望能够帮助更多的小伙伴. 我本科毕业于四川师范学院计算机系,刚毕业时也是小白,也是一步步成 ...

  5. Intel 基辛格:每家科技公司都应该有一位技术CEO

    整理|杨阳 出品 | CSDN(ID:CSDNnews) 在Intel CEO基辛格的最新采访中,他表示说:"科技公司CEO永远不应该偏离技术太远.每家科技公司都应该有一位技术CEO,这是管 ...

  6. Kaldi语音识别技术(五) ----- 特征提取

    Kaldi语音识别技术(五) ----- 特征提取 文章目录 Kaldi语音识别技术(五) ----- 特征提取 一.识别流程 二.MFCC特征提取概述 三.文件格式 文件格式说明 提取部分数据 修复 ...

  7. 详解Linux驱动技术(五) _设备阻塞/非阻塞读写

    在Linux驱动程序编写过程中,设备阻塞/非阻塞读写是一种非常重要的技术.它可以实现高效的数据传输和事件处理,提高系统的性能和响应速度.在本文中,我们将深入探讨Linux驱动技术(五) _设备阻塞/非 ...

  8. (数据库系统概论|王珊)第十章数据库恢复技术-第四、五、六、七节:数据库恢复技术和数据库镜像

    文章目录 一:数据库恢复的实现技术 (1)数据转储(备份) A:转储的分类 ①:按照系统是否运行事物时分类 ②:按转储的范围分类 (2)登记日志文件 A:日志文件的内容 B:日志文件的作用 C:登记日 ...

  9. 计算机视觉技术的应用实例,图像识别技术都有哪些?图像识别技术原理及应用实例...

    图像识别是计算机视觉和人工智能领域的重要组成部分,其终极目标是使计算机具有分析和理解图像内容的能力.图像识别是一个综合性的问题,涵盖图像匹配.图像分类.图像检索.人脸检测.行人检测等技术,并在互联网搜 ...

最新文章

  1. Java正则入门(1)——去除字符串头尾空格
  2. docker容器内部无法ping通域名?
  3. 通过WebAssembly在浏览器运行PHP
  4. Linux中压缩、解压缩(tar/zip/bzip2/gz/gzip/zip)
  5. tensorflow学习(4.loss函数以及正则化的使用 )
  6. 洛谷 P1410 子序列(DP)
  7. 如何避免fstab挂载故障问题
  8. 大话设计模式—代理模式
  9. Groovy里使用Runnable实现多线程
  10. Python turtle库实现基本剖析
  11. Mysql 的一些基本用法
  12. 实战CGLib系列之proxy:方法拦截MethodInterceptor
  13. 2.7.3-YARN-获取debug命令:resourceManager+nodeManager
  14. Unity应用架构设计(10)——绕不开的协程和多线程(Part 1)
  15. kernel开启启动log_MySql启动数据库设置初始密码
  16. django 发送php,django 使用 request 获取浏览器发送的参数
  17. 1.Windows下 PHP 开源框架 laravel 的搭建
  18. YUV420 总结 (YU12、YV12、NV12 和 NV21)
  19. lane是什么意思_Lane_英文名Lane是什么意思
  20. 新版HyperMesh的Assemblies中调出下拉菜单查看Component(装配关系模型树)

热门文章

  1. 2013-9-7中文幽默演讲比赛-我的演讲之路
  2. 不断压抑情绪会我们失去什么?
  3. conda创建指定python版本environment
  4. c语言vsprintf函数,vsprintf函数
  5. 智能交通系统,可以提高通行效率,节约能源,降低空气污染的水平
  6. R软件与RStudio安装(版本R-4.2.2)Windows10
  7. 阿里云 IaaS 基础设施能力拿下全球第一
  8. Java实现二分法排序
  9. 优思学院|建立六西格玛管理模式的七部曲
  10. matlab fts2mat,[原创]基于MATLAB的通达信股价数据的复权处理(fantuanxiaot版本)