什么是数据库

一、数据库

每个人家里都会有冰箱,冰箱是用来干什么的?冰箱是用来存放食物的地方。
同样的,数据库是存放数据的地方。正是因为有了数据库后,我们可以直接查找数据。例如你每天使用余额宝查看自己的账户收益,就是从数据库读取数据后给你的。

数据库(database)
保存有组织的数据的容器(通常是一个文件或一组文件)。

注意:误用导致混淆
人们通常用数据库这个术语来代表他们使用的数据库软件,这是不正确的,也因此产生了许多混淆。确切地说,数据库软件应称为数据库管理系统(DBMS)。数据库是通过 DBMS 创建和操纵的容器,而具体它究竟是什么,形式如何,各种数据库都不一样。

二、表

你往文件柜里放资料时,并不是随便将它们扔进某个抽屉就完事了的,而是在文件柜中创建文件,然后将相关的资料放入特定的文件中。
数据库领域中,这种文件称为表。表是一种结构化的文件,可用来存储某种特定类型的数据。表可以保存顾客清单、产品目录,或者其他信息清单。
表(table)
某种特定类型数据的结构化清单。

这里的关键一点在于,存储在表中的数据是同一种类型的数据或清单。决不应该将顾客的清单与订单的清单存储在同一个数据库表中,否则以后的检索和访问会很困难。应该创建两个表,每个清单一个表。
数据库中的每个表都有一个名字来标识自己。这个名字是唯一的,即数据库中没有其他表具有相同的名字。
说明:表名
使表名成为唯一的,实际上是数据库名和表名等的组合。有的数据库还使用数据库拥有者的名字作为唯一名的一部分。也就是说,虽然在一个数据库中不能两次使用相同的表名,但在不同的数据库中完全可以使用相同的表名。

表具有一些特性,这些特性定义了数据在表中如何存储,包括存储什么样的数据,数据如何分解,各部分信息如何命名等信息。描述表的这组信息就是所谓的模式(schema),模式可以用来描述数据库中特定的表,也可以用来描述整个数据库(和其中表的关系)。
模式
关于数据库和表的布局及特性的信息。

三、列和数据类型

表由列组成。列存储表中某部分的信息。
列(column)
表中的一个字段。所有表都是由一个或多个列组成的。

理解列的最好办法是将数据库表想象为一个网格,就像个电子表格那样。网格中每一列存储着某种特定的信息。例如,在顾客表中,一列存储顾客编号,另一列存储顾客姓名,而地址、城市、州以及邮政编码全都存储在各自的列中。
提示:数据分解
正确地将数据分解为多个列极为重要。例如,城市、州、邮政编码应该总是彼此独立的列。通过分解这些数据,才有可能利用特定的列对数据进行分类和过滤(如找出特定州或特定城市的所有顾客)。如果城市和州组合在一个列中,则按州进行分类或过滤就会很困难。
你可以根据自己的具体需求来决定把数据分解到何种程度。例如,一般可以把门牌号和街道名一起存储在地址里。这没有问题,除非你哪天想用街道名来排序,这时,最好将门牌号和街道名分开。

数据库中每个列都有相应的数据类型。数据类型(datatype)定义了列可以存储哪些数据种类。例如,如果列中存储的是数字(或许是订单中的物品数),则相应的数据类型应该为数值类型。如果列中存储的是日期、文本、注释、金额等,则应该规定好恰当的数据类型。
数据类型
允许什么类型的数据。每个表列都有相应的数据类型,它限制(或允许)该列中存储的数据。

数据类型限定了可存储在列中的数据种类(例如,防止在数值字段中录入字符值)。数据类型还帮助正确地分类数据,并在优化磁盘使用方面起重要的作用。因此,在创建表时必须特别关注所用的数据类型。
注意:数据类型兼容
数据类型及其名称是 SQL 不兼容的一个主要原因。虽然大多数基本数据类型得到了一致的支持,但许多高级的数据类型却没有。更糟的是,偶然会有相同的数据类型在不同的 DBMS 中具有不同的名称。对此用户毫无办法,重要的是在创建表结构时要记住这些差异。

四、行

表中的数据是按行存储的,所保存的每个记录存储在自己的行内。如果将表想象为网格,网格中垂直的列为表列,水平行为表行。
例如,顾客表可以每行存储一个顾客。表中的行编号为记录的编号。
行(row)
表中的一个记录。

说明:是记录还是行?
你可能听到用户在提到行时称其为数据库记录(record)。这两个术语多半是可以互通的,但从技术上说,行才是正确的术语。

五、主键

表中每一行都应该有一列(或几列)可以唯一标识自己。顾客表可以使用顾客编号,而订单表可以使用订单 ID。雇员表可以使用雇员 ID。书目表则可以使用国际标准书号 ISBN。
主键(primary key)
一列(或几列),其值能够唯一标识表中每一行。

唯一标识表中每行的这个列(或这几列)称为主键。主键用来表示一个特定的行。没有主键,更新或删除表中特定行就极为困难,因为你不能保证操作只涉及相关的行,没有伤及无辜。
提示:应该总是定义主键
虽然并不总是需要主键,但多数数据库设计者都会保证他们创建的每个表具有一个主键,以便于以后的数据操作和管理。

表中的任何列都可以作为主键,只要它满足以下条件:

  • 任意两行都不具有相同的主键值;
  • 每一行都必须具有一个主键值(主键列不允许空值 NULL);
  • 主键列中的值不允许修改或更新;
  • 主键值不能重用(如果某行从表中删除,它的主键不能赋给以后的新行)。

主键通常定义在表的一列上,但并不是必须这么做,也可以一起使用多个列作为主键。在使用多列作为主键时,上述条件必须应用到所有列,所有列值的组合必须是唯一的(但其中单个列的值可以不唯一)。

数据库分类

一、关系型数据库

1、 Oracle

oracle公司推出的数据库系统。优势:移植性好、使用方便、功能性强、适用于大、中、小微机环境。

2、SQL Server

Microsoft 公司推出的数据库系统,广泛应用于电子商务、银行、保险、电力等行业。 缺点:只能在 Windows 平台上运行。

3、 MySQL

开放源码数据库。优点:开源、快捷、跨平台性、方便、免费等特点。

二、非关系型数据库

memcached (纯内存),redis(持久化缓存),mongodb(文档的数据库

数据库事务

一、事务理解

事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。

事务最经典的例子就是转账:
操作: 张三和李四各自的账号都是1000元;张三向李四转账100元
组成单元: 张三钱-100, ls钱+100
操作成功: 张三钱900,ls钱1100
操作失败: 张三钱1000,ls钱1000
不可能发生: 张三钱900,ls钱1000; zs钱1000,ls钱1100

二、MySQL进行事务管理

1.自动事务(mysql默认)

-- 场景: zs向ls转账100-- zs钱-100 ls钱+100
-- 自动事务管理: MySQL默认就是自动事务管理(自动开启事务,自动提交事务),一条sql语句就是一个事务
update account set money = money - 100 where name = 'zs';
-- 异常
update account set money = money + 100 where name = 'ls';

2.手动开启一个事务

方式一:

start transaction;开启事务
commit;提交
rollback;回滚

-- 没有异常
start transaction; -- 开启事务
update account set money = money - 100 where name = 'zs'; -- zs钱-100
-- 没有异常
update account set money = money + 100 where name = 'ls'; -- ls钱 +100
commit; -- 提交事务
 
 
-- 有异常
start transaction; -- 开启事务
update account set money = money - 100 where name = 'zs'; -- zs钱-100
-- 有异常
update account set money = money + 100 where name = 'ls'; -- ls钱 +100
rollback; -- 回滚事务

1680158321921.jpeg

方式二:

设置MYSQL中的自动提交的参数

查看MYSQL中事务是否自动提交
show variables like '%commit%';
 
设置自动提交的参数为OFF
set autocommit = 0;-- 0:OFF  1:ON

三、回滚点

1.什么是回滚点?

    在某些成功的操作完成之后,后续的操作有可能成功有可能失败,但是不管成功还是失败,前面操作都已经成功,可以在当前成功的位置设置一个回滚点。可以供后续失败操作返回到该位置,而不是返回所有操作,这个点称之为回滚点。

2.回滚点的操作语句

1680158897631.png

3.具体操作

  1. 将数据还原到1000
  2. 开启事务
  3. 让张三账号减3次钱
  4. 设置回滚点:savepoint three_times;
  5. 让张三账号减4次钱
  6. 回到回滚点:rollback to three_times;
    总结:设置回滚点可以让我们在失败的时候回到回滚点,而不是回到事务开启的时候。
start transaction;
update account set money = money - 100 where name = 'zs'; -- zs账户-100
update account set money = money - 100 where name = 'zs'; -- zs账户-100
update account set money = money - 100 where name = 'zs'; -- zs账户-100
update account set money = money - 100 where name = 'zs'; -- zs账户-100
update account set money = money - 100 where name = 'zs'; -- zs账户-100
-- 以上sql语句没有问题
savepoint abc;
update account set money = money - 100 where name = 'zs'; -- zs账户-100
update account set money = money - 100 where name = 'zs'; -- zs账户-100
-- 出现异常,回滚到abc回滚点位置
rollback to abc;
update account set money = money - 100 where name = 'zs'; -- zs账户-100
update account set money = money - 100 where name = 'zs'; -- zs账户-100
update account set money = money - 100 where name = 'zs'; -- zs账户-100
commit;

4.应用场景

插入大量的数据的时候. 1亿条数据 需要插入很久. 要求: 1亿条数据是一个整体,要么全部插入成功的 要么都不插入成功.
注意:

  • 建议手动开启事务, 用一次 就开启一次
  • 开启事务之后, 要么commit, 要么rollback
  • 一旦commit或者rollback, 当前的事务就结束了
  • 回滚到指定的回滚点, 但是这个时候事务没有结束的

四、事务的四大特性(ACID)

  • 关系性数据库需要遵循ACID规则,具体内容如下:

1680158529034.jpeg
** 原子性(Atomicity)**原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

eg: zs 1000; ls 1000; 
	zs 给 ls转100
	要么都发生zs 900; ls 1100;
	要么都不发生zs 1000; ls 1000;

**一致性(Consistency)**事务前后数据的完整性必须保持一致。

eg: zs 1000; ls 1000;  一共2000
	zs 给 ls转100
	要么都发生zs 900; ls 1100; 	一共2000
	要么都不发生zs 1000; ls 1000; 一共2000

**持久性(Durability)**持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

eg: zs 1000 给小红 转520, 张三 提交了

**隔离性(Isolation)**事务的隔离性是指多个用户并发操作数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。 简单来说: 事务之间互不干扰。
如果不考虑隔离性,会引发下面的问题:
事务在操作时的理想状态: 所有的事务之间保持隔离,互不影响。因为并发操作,多个用户同时访问同一个数据。可能引发并发访问的问题。

1680160249498.jpeg

五、事务隔离级别

有四种隔离级别,分别是读未提交(Read uncommitted)读已提交(Read committed)可重复读(Repeatable read)可串行化(Serializable),用来解决数据库操作中产生的各种问题。

1、读未提交(Read uncommitted)

在这种隔离级别下,所有事务能够读取其他事务未提交的数据。读取其他事务未提交的数据,会造成脏读。因此在该种隔离级别下,不能解决脏读、不可重复读和幻读。
读未提交可能会产生脏读的现象,那么怎么解决脏读呢?那就是使用读已提交。

2、读已提交(Read committed)

在这种隔离级别下,所有事务只能读取其他事务已经提交的内容。能够彻底解决脏读的现象。但在这种隔离级别下,会出现一个事务的前后多次的查询中却返回了不同内容的数据的现象,也就是出现了不可重复读。
注意【1】这是大多数数据库系统默认的隔离级别,例如Oracle和SQL Server,但mysql不是。
已提交可能会产生不可重复读的现象,我们可以使用可重复读。

3、可重复读(Repeatable read)

在这种隔离级别下,所有事务前后多次的读取到的数据内容是不变的。也就是某个事务在执行的过程中,不允许其他事务进行update操作,但允许其他事务进行add操作,造成某个事务前后多次读取到的数据总量不一致的现象,从而产生幻读。
注意【1】这才是mysql的默认事务隔离级别
可重复读依然会产生幻读的现象,此时我们可以使用串行化来解决。

4、可串行化(Serializable)

在这种隔离级别下,所有的事务顺序执行,所以他们之间不存在冲突,从而能有效地解决脏读、不可重复读和幻读的现象。但是安全和效率不能兼得,这样事务隔离级别,会导致大量的操作超时和锁竞争,从而大大降低数据库的性能,一般不使用这样事务隔离级别。


image.png

六、脏读、不可重复读、幻读

理解这三种由于并发访问导致的数据读取问题,再理解事务隔离级别就简单多了。

【1】脏读(读取未提交数据)

A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的数据就是脏数据。就好像原本的数据比较干净、纯粹,此时由于B事务更改了它,这个数据变得不再纯粹。这个时候A事务立即读取了这个脏数据,但事务B良心发现,又用回滚把数据恢复成原来干净、纯粹的样子,而事务A却什么都不知道,最终结果就是事务A读取了此次的脏数据,称为脏读。

这种情况常发生于转账与取款操作中
image.png

【2】不可重复读(前后多次读取,数据内容不一致)

事务A在执行读取操作,由整个事务A比较大,前后读取同一条数据需要经历很长的时间 。而在事务A第一次读取数据,比如此时读取了小明的年龄为20岁,事务B执行更改操作,将小明的年龄更改为30岁,此时事务A第二次读取到小明的年龄时,发现其年龄是30岁,和之前的数据不一样了,也就是数据不重复了,系统不可以读取到重复的数据,成为不可重复读。
image.png

【3】幻读(前后多次读取,数据总量不一致)

事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读。
image.png

数据库

一、全局锁

加全局锁命令:flush table with read lock;(FTWRL

mysql> flush table with read lock;
Query OK, 0 rows affected (0.05 sec)

释放全局锁命令:unlock tables;

mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)

1. 全局锁的特点

全局锁让整个数据库(所有表)处于只读状态,使用这个命令后,数据库表的增删改(DML)、表结构的更改(DDL)、更新类事物的提交都会被阻塞
例如下面,前面我已经给该数据库加上了全局锁,此时对其中一个表进行查询和插入操作:

mysql> select * from test;
+----+------+-----------+
| id | name | adress    |
+----+------+-----------+
|  1 | yy   | ChongQing |
|  2 | lch  | XiAn      |
+----+------+-----------+
2 rows in set (0.00 sec)

mysql> insert into test values('yyg','zhongxian');
ERROR 1223 (HY000): Can't execute the query because you have a conflicting read lock

可以看到,查询是允许的,而插入是禁止的;

2. 全局锁的作用(全库逻辑备份)

上面看到了全局锁会让数据库只处于可读的状态,这种状态会使数据库处于一个多么低效率的状态,那么为什么还需要它呢?
低效率的原因:

  • 如果你在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆;
  • 如果你在从库上备份,那么备份期间从库不能执行主库同步过来的binlog,会导致主从延迟;

因为在以前,全局锁的主要作用就是:做全库逻辑备份;
即在备份的时候,加上全局锁,让表只处于可读状态,处于这种
那么为什么这么做呢?即为什么需要在备份的时候加全局锁呢,这里用反证法来证明:
:::info
**案例:**假如一个商城里有两张表,一张用户所购商品表,一张是用户余额表,假如在备份商品表刚完成还没开始备份用户余额表的时候,一位用户购买了某个产品,此时它的余额扣除成功,然后备份了用户余额表,这时造成的现象就是:备份的商品表没有用户买的那个商品,但备份的余额表却扣除了钱;
假如是备份完余额表,用户下单,再备份商品表的话,结果就是:用户的余额没扣,却多了商品;
:::
上面的案例说明了,在做全库逻辑备份的时候,如果不加锁,**会造成备份得到的库里面的表不是一个逻辑时间点 **,这个视图是逻辑不一致的;那么提到这,对于视图一致性,事物的可重复读这个隔离性不就能够实现吗;所以官方自带逻辑备份工具是mysqldump。当mysqldump使用参数-single-transaction的时候,导数据之前就会启动一个事物,来确保拿到一致性视图。而由于MVCC的支持,这个过程中数据是可以正常更新的;
那么,有了mysqldump这个功能,为什么还需要FTWRL?(⭐)
因为mysqldump是基于事物的,而有些引擎不支持事物,比如MyISAM,这种引擎在做全库逻辑备份的时候就只能使用全局锁了;
对于全库只读,还有一种方式可以实现:

set global readonly = true

那么到底使用set的方式还是使用FTWRL的方式来进行全库逻辑备份呢?这里有两个原因推荐使用FTWRL:

  1. 在有些系统里,readonly的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库,因此修改global变量的影响比较大,不建议使用;
  2. 两者在异常处理机制上有差异:
  • 执行FTWRL命令之后由于客户端的发生异常断开,那么MySQL会自动释放这个全局锁,整个库可以回到正常更新的状态;
  • 执行set global这个方式的话,如果客户端发生异常,则数据库还是一直会保持只读状态,这样会导致整个库长时间处于不可写状态,风险较高;

二、表级锁

MySQL里面表级别的锁分两种:

  • 表锁
  • 元数据锁(mete data lock,MDL)

1. 表锁

加锁命令: lock tables 表名 read/write

mysql> lock table test read;
Query OK, 0 rows affected (0.00 sec)

释放锁的命令:unlock tables;

1. 特点
  • 当还没有出现更细粒度的锁时,表锁是常用的处理并发问题的方式,而对于InnoDB这种支持行锁的引擎,一般不适用表锁,因为表锁的影响效率还是很大;
  • 对某个表加表锁锁,不仅影响其他线程对该表的对应操作,也会影响当前线程对这张表的操作,例如:
mysql> lock table test read;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into test(name,adress) values('ygz','zhongxian');
ERROR 1099 (HY000): Table 'test' was locked with a READ lock and can't be updated

上面表示了对该表加读锁后,自己也不能对其进行修改;自己和其他线程只能读取该表;
当对某个表执加上写锁后(lock table t1 write),该线程可以对这个表进行读写,其他线程对该表的读和写都受到阻塞; (⭐)
例子:启动一个命令行(线程)连接数据库,对test表进行加上写锁,然后在该线程中执行读和写:

mysql> insert into test(name,adress) values('zsf','zhongxian');
Query OK, 1 row affected (0.64 sec)

mysql> select * from test;
+----+------+-----------+
| id | name | adress    |
+----+------+-----------+
|  1 | yy   | ChongQing |
|  2 | lch  | XiAn      |
|  3 | yyg  | zhongxian |
|  4 | ygz  | zhongxian |
|  5 | zsf  | zhongxian |
+----+------+-----------+
5 rows in set (0.00 sec)

然后启动另外一个线程(重新打开一个cmd连接该数据库),然后执行查询test表,如下,回车后将会阻塞于此,处于无结果状态(ctrol+c可以撤销):

mysql> select * from test;

当在第一个线程中执行unlock tables后(即释放这个写锁),第二个线程的查询马上就有了结果;
同样,在第二个线程中对该表的更新也是一样的效果;

三、行锁

行锁是在引擎层由各个引擎自己实现的,有的引擎并不支持行锁,比如MyISAM就不支持行锁,这意味着:

  • 并发控制只能使用表锁,对于这种引擎(MyISAM)的表,同一张表上任何时刻只能有一个更新在执行,这严重影响了并发度
  • InnoDB是支持行锁的,这也是MyISAM被InnoDB代替的主要原因;

1. 行锁特性

首先注意:InnoDB的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则都会从行锁升级为表锁; (⭐)
下面来看一个例子:
开启两个cdm窗口,启动两个事物A、B,在事物A中更改表中的一行数据,此时未提交事物A,再在事物B中查询该表,会发现查询的结果是A未修改的结果,也就是事物A还没提交,它对表test的更新对B不可见
事物A:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> update test set name='WangWu' where id=1;
Query OK, 1 row affected (0.04 sec)
Rows matched: 1  Changed: 1  Warnings: 0

事物B:(可以看到还是原来的数据)

mysql> select * from test;
+----+---------+-----------+
| id | name    | adress    |
+----+---------+-----------+
|  1 | zangsan | ChongQing |
|  2 | lch     | XiAn      |
|  3 | yyg     | zhongxian |
|  4 | ygz     | zhongxian |
|  5 | zsf     | zhongxian |
+----+---------+-----------+
5 rows in set (0.02 sec)

此时再做一个实验,(事物A还未提交),在事物B中更改其他行,看是否能成功:

mysql> update test set name='LiSi' where id=2;
Query OK, 1 row affected (0.11 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from test;
+----+---------+-----------+
| id | name    | adress    |
+----+---------+-----------+
|  1 | zangsan | ChongQing |
|  2 | LiSi    | XiAn      |
|  3 | yyg     | zhongxian |
|  4 | ygz     | zhongxian |
|  5 | zsf     | zhongxian |
+----+---------+-----------+
5 rows in set (0.00 sec)

mysql> update test set name='LiSi' where id=1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

可以看到,在事物A未提交的情况下:

  • B中不能更新A中更新的那一行(会受到阻塞,一定时间如果还没获取到行锁会自动放弃更新),其他行都能更新;
  • 当A一提交,B中更新A中更新的那一行就会不再阻塞,执行完毕;

为验证行锁是建立在索引之上的,我们在在事物A中不用id更新test表,如下:
(id是主键,所以是有索引的)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> update test set name='TaoLiu' where name='ygz';
Query OK, 1 row affected (0.02 sec)
Rows matched: 1  Changed: 1  Warnings: 0

此时在事物B中更新另外一行:

mysql> update test set name='ZangLiu' where id=2;

发现阻塞于此,没有更新同一行啊,为什么会被锁住?
因为这里事物A中的更新没有基于索引(name没加索引),所以这里由行锁会降级成表锁,所以在事物B中不能对该表进行任何更新,只能读;

2. 两阶段锁协议

在InnoDB事物中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事物提交了才会释放,这个就是两阶段锁协议;
知道了这个协议后,对我们的某些开发会得到效率提升,比如:

  • 如果你的事物中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放

四、死锁

死锁这个名词相信大家都不陌生,同样数据库也会有死锁的出现,这里举一个例子(以行锁导致的死锁为例):
image.png
如上图,当执行完事物B的update最后一句时,回车出现如下:

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

1. 处理死锁策略

(1):直接进入等待,直到超时,这个超时时间可以通过参数innodb_lock_wait_timeout来进行设置,如下:(InnoDB中查看这个参数默认是50秒

mysql> show variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 50    |
+--------------------------+-------+
1 row in set, 1 warning (0.00 sec)

设置的话,语句是:

mysql> set innodb_lock_wait_timeout = 50;
Query OK, 0 rows affected (0.00 sec)

2)发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事物,让其他事物得以执行;
将参数innodb_deadlock_detect设置为on,就代表开启; (InnoDB默认开启死锁检测)

mysql> show variables like 'innodb_deadlock_detect';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| innodb_deadlock_detect | ON    |
+------------------------+-------+
1 row in set, 1 warning (0.04 sec)

设置语句为:

mysql> set global innodb_deadlock_detect = on;
Query OK, 0 rows affected (0.05 sec)
  • 两种方案区别:
    • 第一种等待50s,这显然对于在线服务起来说是等不起的,时间设置太短又会造成误判;
    • 所以一般采用第二种:死锁检测,InnoDB本身默认就是将那个参数设置为on的,但这种方式也是有弊端的,毕竟死锁检测需要消耗资源,具体详细下面来讲;

2. 死锁检测(⭐)

上面讲了,死锁检测是数据库检验死锁的一个策略,检测所需要的代价就是:

  • 每当一个事物被锁住的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,即死锁;

进行死锁检测的条件:

  • 当前事物需要加锁访问的行上被别人锁住时,才会进行死锁检测

注意点:

  • 一致性读的时候不会加锁,所以不用死锁检测
  • 并不是每次死锁检测都要扫描所有的事物,比如下面这种情况:
    B在等A
    D在等C
    现在事物E来了,发现E需要等D,则此时E需要判断跟D、C是否成环(形成死锁),并不会去检测B和A,因为他们访问的肯定不是同一个资源;

3. 典型案例(CPU利用率高,但效率低的场景)

那么假如出现这样一个场景:
一千个事物要同时更新test表中的同一行数据,这时其实并不会发生死锁,但会发现效率极低,这是为什么呢
因为每一个被(行锁)堵住的线程都会去判断是不是由于自己的加入导致了死锁,这是一个时间复杂度为O(n)的操作,一千个事物,此时时间复杂度高达100万这个数量级,虽然最终检测没有死锁,但是期间消耗了大量的CPU资源,所以你将会看到,CPU利用率很高,但是却执行不了几个事物;
那么怎么解决上面这种问题呢?

  • (1)如果你确认你的操作中不会出现死锁,就关闭死锁检测;当然这种方法风险是很大的,毕竟死锁的出现不是我们能预估的,一旦出现,就会造成超时等待;
  • (2)控制并发度;
    • 在上面的例子中,是同时有大量线程去更新同一行导致的,假如把并发度降到很低就不会出现时间复杂度过大的死锁检测了,具体做法就是对于更新相同行数据的线程,在进入引擎前排队,这样在InnoDB里面就不会同时有大量的死锁检测工作了;
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐