数据库进阶
B 树索引
什么是索引?
MySQL官方对索引的定义为:索引就是用于实现数据的快速检索,由数据表中的一列或多列组合而成,索引实质上是一张描述索引列的列值与原表中记录行之间一 一对应关系的有序表。索引的实现通常使用B树及其变种B 树。
更通俗的说,索引就相当于目录。为了方便快速查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的。
索引有哪些优缺点?
优点
-
提高数据检索的效率,降低数据库的IO成本(不需要全表扫描)
-
通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗
缺点
-
创建和维护索引组要耗费时间,并且随着数据量的增加所耗费的时间也会增加。
-
索引需要占磁盘空间,除了数据表占数据空间以外,每一个索引还要占一定的物理空间。如果有大量的索引,索引文件可能比数据文件更快达到最大文件尺寸。
-
当对表中的数据进行增加、删除和修改的时候,索引也要动态维护,这样就降低了数据的维护速度。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件
什么是聚簇索引和非聚簇索引?何时使用聚簇索引与非聚簇索引?
根据索引的存储方式来划分,索引可以分为聚簇索引和非聚簇索引。聚簇索引的特点是叶子节点包含了完整的记录行,而非聚簇索引的叶子节点只有索引字段和主键ID。
聚簇索引
聚簇索引也叫聚集索引或主键索引,它实际上并不是一种单独的索引类型,而是一种数据存储方式,聚簇索引的叶子节点保存了一行记录的所有列信息。也就是说聚簇索引的叶子节点中包含了一个完整的记录行
特点
-
一个表只能创建一个聚集索引;
-
聚集索引尽量建在不会经常发生变动的列上,因为一旦列变动同时也会引索引结构变化,而索引结构中也包含者数据的变动;
-
数据库在创建主键时如果这个表之前没有聚集索引,同时建立主键时候没有强制指定使用非聚集索引,则建立主键时候,同时建立一个唯一的聚集索引
非聚簇索引
非聚簇索引也叫辅助索引或普通索引或二级索引,它的叶子节点只包含主键值和索引字段,通过非聚簇索引查找记录要先找到主键,然后通过主键再到聚簇索引中找到对应的记录行,这个过程被称为回表
澄清一个概念:innodb中,在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值。
例如一个包含了用户姓名和年龄的的数据表,假设主键是用户ID,聚簇索引的结构为(橙色的代表id,绿色是指向子节点的指针):
叶子节点中,为了突出记录,把(id, name, age)区分开来了,实际上是连在一起的,它们是构成一条记录的整体。
而一个非聚簇索引(以age为索引)的结构是:
它的叶子节点中,不包含整个记录的完整信息,除了索引字段age本身以外,只包含当前记录的主键id。如果想要获取整行记录数据还需要再通过id号到聚簇索引中回表查询。
InnoDB中,每个表必须有一个聚簇索引,默认是根据主键建立的。如果表中没有主键,InnoDB会选择一个合适唯一的非空索引列作为聚簇索引,如果找不到合适的列,会使用一列隐藏的列DB_ROW_ID作为聚簇索引。
联合索引是什么?为什么需要注意联合索引中的顺序?
联合索引是什么?
联合索引(也叫组合索引)指的是同时对多列创建的索引,创建联合索引后,叶子节点会同时包含所有索引列的值和主键id,并且同时根据多列排序,在联合索引中,如果想要命中索引,需要按照建立索引时的字段顺序挨个使用,否则无法命中索引。
例如一个包含了用户姓名和年龄的的数据表,假设主键是用户ID,聚簇索引的结构为(橙色的代表id,绿色是指向子节点的指针):
例如对同时对上面的姓名和年龄创建的索引结构:
每个叶子节点同时保存了所有的索引列,除此之外,还是只包含了主键id。
最左前缀匹配原则
当对多列创建索引后,并不是只要包含了创建索引的列就能使用索引,索引的使用要遵循最左前缀匹配原则。
假设对列(A, B, C)创建索引,那么只有以下场景能使用索引:
对列(A, B, C)或者(A, C)或者(A, B)进行查询会匹配索引,对(C, A)或者(B, C)来说不能使用索引,也就是都包含了第一个索引A,整个联合索引才会起效
-
通配符只能使用LIKE 'val%'形式,不能使用LIKE '%VAL%',后者会导致全表扫描。
-
索引列不能进行运算,例如WHERE A 1 = 5这种场景会导致索引失效。
-
索引列不能包含范围值查询,如LIKE/BETWEEN/>/<等都会导致后面的列无法匹配索引。
-
索引列不能包含有NULL值。
为什么需要注意联合索引中的顺序?
MySQL使用联合索引时需要索引有序,假设现在建立了"name,age,school"的联合索引,那么索引的排序为: 先按照name排序,如果name相同,则按照age排序,如果age的值也相等,则按照school进行排序。
当进行查询时,此时索引仅仅按照name严格有序,因此必须首先使用name字段进行等值查询,之后对于匹配到的列而言,其按照age字段严格有序,此时可 以使用age字段用做索引查找,以此类推。因此在建立联合索引的时候应该注意索引列的顺序,一般情况下,将查询需求频繁或者字段选择性高的列放在前面。此外可以根据特例的查询或者表结构进行单独的调整。
索引有哪几种类型?
普通索引
普通索引是最基本的索引,它没有任何限制,值可以为空;仅加速查询。
-
CREATE INDEX index_name ON table(column)
-
ALTER TABLE table_name ADD INDEX index_name (column)
唯一索引
唯一索引与普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。简单来说:唯一索引是加速查询 列值唯一(可以有null)。
-
CREATE UNIQUE INDEX indexName ON table(column(length))
-
ALTER TABLE table_name ADD UNIQUE indexName (column(length))
主键索引
主键索引是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。简单来说:主键索引是加速查询 列值唯一(不可以有null) 表中只有一个。主键索引一般建议使用数据表的自增唯一主键来作为主键索引使用。主键索引简称主键,原文是PRIMARY KEY,由一个或多个列组成,用于唯一性标识数据表中的某一条记录。一个表可以没有主键,但最多只能有一个主键,并且主键值不能包含NULL。主键索引属于唯一索引的一个特殊种类,一个表的某列创建了主键索引后会具备唯一索引的功能同时还会对该列生成主键约束,所以简单来说主键索引是一种带有主键约束的唯一索引;
特点
-
数据库在创建主键同时会自动建立一个唯一索引。
-
每个表最多只能创建一个主键索引;
-
创建了主键索引的列不允许有重复的值,并且不能为null值;
-
创建了主键索引的列可以作为外键;
-
ALTER TABLE 表名 ADD PRIMARY KEY 索引名(column_list);
组合索引
组合索引指在多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀原则。
可以说:组合索引是多列值组成的一个索引,专门用于组合搜索,其效率大于索引合并。
ALTER TABLE 表名 ADD INDEX 索引名 (列名1,列名2,列名3);
全文索引
全文索引主要用来查找文本中的关键字,而不是直接与索引中的值相比较。全文索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的where语句的参数匹配。全文索引配合match against操作使用,而不是一般的where语句加like。它可以在create table,alter table ,create index使用,不过目前只有char、varchar,text 列上可以创建全文索引。值得一提的是,在数据量较大时候,现将数据放入一个没有全文索引的表中,然后再用CREATE index创建全文索引要比先为一张表建立全文索引然后再将数据写入的速度快很多。
-
CREATE TABLE `table` (
-
`id` int(11) NOT NULL AUTO_INCREMENT ,
-
`title` char(255) CHARACTER NOT NULL ,
-
`content` text CHARACTER NULL ,
-
`time` int(10) NULL DEFAULT NULL ,
-
PRIMARY KEY (`id`),
-
FULLTEXT (content)
-
-
);
-
ALTER TABLE 表名 ADD FULLTEXT 索引名(索引列名)
-
CREATE FULLTEXT INDEX 索引名 ON 表名(索引列名)
简单来说:全文索引是对文本的内容进行分词,进行搜索。
DROP INDEX index_name ON table//删除指定索引
优化和Explain
explain执行计划(熟练)
例如使用explain 命令可以用来分析select 语句的运行效果。
explain select * from mytest;
也可以直接在Navicat上直接解释对应的sql语句
explain分析结果说明
select_type:每个子查询的查询类型
table:查询的数据表
type:访问类型(非常重要,可以看出有没有走索引),有以下几个值:
编辑 possible_keys:可能使用的索引,注意不一定会使用。查询涉及到的字段上若存在索引,则该索引将被列出来。当该列为 NULL时就要考虑当前的SQL是否需要优化了。
key:显示MySQL在查询中实际使用的索引,若没有使用索引,显示为NULL。
key_length:索引长度
ref:表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值 rows 返回估算的结果集数目,并不是一个准确的值。
优化(熟练)
30种SQL查询语句优化方法
-
应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描
-
对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引
-
应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null,可以在num列上设置默认值 0 ,确保表中num列没有null值,然后这样查询:select id from t where num=0
-
尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num=10 or num=20可以这样查询:select id from t where num=10 union all select id from t where num=20
-
下面的模糊查询也将导致全表扫描:select id from t where name like ‘%c%’,若要提高效率,可以使用向左匹配 select id from t where name like c% (不能前置百 分号),或者考虑全文检索。
-
in 和 not in 也要慎用,否则会导致全表扫描,如:select id from t where num in(1,2,3),对于连续的数值,能用 between 就不要用 in 了:select id from t where num between 1 and 3
-
如果在 where 子句中使用参数,也会导致全表扫描。因为 SQL 只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:select id from t where num=@num可以改为强制查询使用索引:select id from t with(index(索引名)) where num=@num
-
应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:select id from t where num/2=100应改为:select id from t where num=100*2
-
应尽量避免在 where 子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:select id from t where substring(name,1,3)=’abc'生成的id应改为:select id from t where name like ‘abc%’。如:select id from t where datediff(day,createdate,’2005-11-30′)=’2005-11-30应改为:select id from t where createdate>=’2005-11-30′ and createdate<’2005-12-1′
-
不要在 where 子句中的 “=” 左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
-
在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使 用,并且应尽可能的让字段顺序与索引顺序相一致。
-
不要写一些没有意义的查询,如需要生成一个空表结构:select col1,col2 into t from t where 1=0 ,这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:create table t(…)
-
很多时候用 exists 代替 in 是一个好的选择:select num from a where num in(select num from b),用下面的语句替换:select num from a where exists(select 1 from b where num=a.num)
-
并不是所有索引对查询都有效,SQL 是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL 查询可能不会去利用索引,如一表中有字段 sex、male、female几乎各一半,那么即使在 sex 上建了索引也对查询效率起不了作用。
-
索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过 6 个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。
-
应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
-
尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会 逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
-
尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
-
任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
-
尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
-
避免频繁创建和删除临时表,以减少系统表资源的消耗。
-
临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
-
在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
-
如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
-
尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过 1 万行,那么就应该考虑改写。
-
使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
-
与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
-
在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
-
尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
-
尽量避免大事务操作,提高系统并发能力。
事务
ACID的概念?
-
原子性(Atomicity):事务中包括的一系列操作要么都做,要么都不做。
-
一致性(Consistency):事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。
-
例:(银行转帐)从帐号A中取出一百元,存入帐号B。
-
实现该转帐业务的事务包括两个操作:
-
1) 给帐户B加上一百元 ( B = B 100 )。
-
2) 从帐户A减去一百元 ( A = A-100 );
-
该事务应该符合:
-
① 这两个操作要么全做,要么全不做(原子性)
-
② 全做或者全不做,数据库都处于一致性状态;如果只做一个操作,数据库就处于不一致性状态,这是不允许的。
-
补充示例: 假设用户A和用户B两者的钱加起来一共是5000,那么如果只有A和B之间转账,无论如何转账,转几次账,那么事务结束后两个用户的钱相加起来必须还是5000,这就是事务的一致性。
-
备注说明: 一致性 和 原子性 比如A转给B 500 ,B账户增加100 如果整个操作成功,那么符合事务的原子性,不符合事务的一致性。 只有B同时增加500这时候才符合事务的一致性。一致性不等同原子性,可能还有其它操作.
-
隔离性(Isolation):一个事务的执行不能被其他事务干扰,而影响它对数据的正确使用和修改。
-
持久性 (Durability):一个事务一旦提交,它对数据库中数据的改变就应该是永久性的,接下来的其他操作或故障不应该对其执行结果有任何影响。
事务概念
开启事务
开启事务两种方式
方式 1
START TRANSACTION 或 BEGIN 开始新的事务
方式 2
SET autocommit = 0;
默认情况下 autocommit = 1,是自动提交事务的。autommit 是 session 级别的,就是当前连接更改了 autocommit,对其他连接没有影响。设置 autocommit 之后,本次连接的所有 sql 都是事务的形式,比如每次 commit 提交。
保存点
那什么是保存点?粗略来讲相当于Java中的“断点”,设置一个断点,当你在数据库中插入一条数据后,在这条语句后面设置一个savepoint,当你需要rollback的时候,只需要回滚到这个savepoint点时就可以,这样就节省了大量的时间,提高的工作效率,也不需要消耗过多的数据库的资源。
START TRANSACTION;//开启事务
INSERT into city VALUES(NULL,"武汉市","7");
SAVEPOINT a;//插入成功后,我们在这设置一个保存点a
INSERT into city VALUES(NULL,"北京市","8");
SAVEPOINT b;//插入成功后,我们在这设置一个保存点b
INSERT into city VALUES("南京市","9");
假设这边sql语句出现错误
rollback to SAVEPOINT a;//这边用保存点进行回滚
//回滚到a成功
提交事务
关闭自动提交后,该位置会作为一个事务起点,直到执行 COMMIT 语句和 ROLLBACK 语句后,该事务才结束。结束之后,这就是下一个事务的起点。简单来说提交事务有COMMIT 语句和 ROLLBACK 语句这两种语句,关闭自动提交功能后,只用当执行 COMMIT 命令后,MySQL 才将数据表中的资料提交到数据库中。如果执行 ROLLBACK 命令,数据将会被回滚。如果不提交事务,而终止 MySQL 会话,数据库将会自动执行回滚操作。
使用 BEGIN 或 START TRANSACTION 开启一个事务之后,自动提交将保持禁用状态,直到使用 COMMIT 或 ROLLBACK 结束事务。之后,自动提交模式会恢复到之前的状态,即如果 BEGIN 前 autocommit = 1,则完成本次事务后 autocommit 还是 1。如果 BEGIN 前 autocommit = 0,则完成本次事务后 autocommit 还是 0。
锁
并发事务带来的问题?
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题:
-
脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
-
丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
-
不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
-
幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
不可重复度和幻读区别:
不可重复读的重点是修改,幻读的重点在于新增或者删除。
例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导致A再读自己的工资时工资变为 2000;这就是不可重复读。
例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假设某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读
行级锁和表级锁
当数据库有并发事务的时候,可能会产生数据的不一致,这时候需要锁机制来保证访问的次序
MyISAM和MEMORY采用表级锁(table-level locking)
BDB采用页面锁(page-level locking)或表级锁,默认为页面锁
InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁
行级锁
(1) 描述
行级锁是mysql中锁定粒度最细的一种锁。表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突,其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁和排他锁
(2) 特点
开销大,加锁慢,会出现死锁。发生锁冲突的概率最低,并发度也最高。
(3) InnoDB有三种行锁的算法
Record Lock(记录锁):单个行记录上的锁。这个也是我们日常认为的行锁。
Gap Lock(间隙锁):间隙锁,锁定一个范围,但不包括记录本身(只不过它的锁粒度比记录锁的锁整行更大一些,他是锁住了某个范围内的多个行【这里主要指的是范围,不是记录本身】,包括根本不存在的数据)。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。该锁只会在隔离级别是RR或者以上的级别内存在。间隙锁的目的是为了让其他事务无法在间隙中新增数据。
Next-Key Lock(临键锁):它是记录锁和间隙锁的结合,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。next-key锁是InnoDB默认的锁
上面这三种锁都是排它锁(X锁)
表级锁
(1) 描述
表级锁是mysql中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分mysql引擎支持。最常使用的MyISAM与InnoDB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)
(2) 特点
开销小,加锁快,不会出现死锁。发生锁冲突的概率最高,并发度也最低。
(3)两种表级锁
LOCK TABLE my_table_name READ; 用读锁锁表,会阻塞其他事务修改表数据。
LOCK TABLE my_table_name WRITE; 用写锁锁表,会阻塞其他事务读和写。
MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。
但是在InnoDB中如果需要表锁就需要显式地声明了。
共享锁和独占锁
共享锁和排他锁(独占锁)是概念,在MySQL中具体的实现就是读锁和写锁:
1、读锁(共享锁)
Shared Locks(S锁),针对同一份数据,多个读操作可以同时进行而不会互相影响,但是不能写。
2、写锁(排它锁)
Exclusive Locks(X锁),当前写操作没有完成前,它会阻断其他写锁和读锁
3、IS锁
意向共享锁、Intention Shared Lock。当事务准备在某条记录上加S锁时,需要先在表级别加一个IS锁。
4、IX锁
意向排他锁、Intention Exclusive Lock。当事务准备在某条记录上加X锁时,需要先在表级别加一个IX锁。
为什么要IS和IX锁?
IS、IX锁是表级锁,它们的提出仅仅为了快速判断表中的记录是否被上行锁,如果没有这个意向锁那就只能用遍历的方式来查看表中有没有上锁的记录。IS锁和IX锁就避免了判断表中行有没有加锁时对每一行的遍历。直接查看表有没有意向锁就可以知道表中有没有行锁。
注意:如果一个表中有多个行锁,他们都会给表加上意向锁,意向锁和意向锁之间是不会冲突的。
innodb元数据锁
数据定义语言DDL(Data Definition Language)
通过CREATE,DROP,ALTER这些命令对逻辑结构等有操作的,其中包括表结构,视图和索引。
数据操纵语言DML(Data Manipulation Language)
通过INSERT,UPDATE,DELETE这些命令对数据进行操作的,对应上面所说的查询操作 DQL与DML共同构建了多数初级程序员常用的增删改查操作。而查询是较为特殊的一种 被划分到DQL中。
元数据锁MDL
元数据锁(metadata lock,简称MDL)是用来保证并发访问数据库对象场景下的一致性而设定的。简单理解,它是为了管理数据库元数据而产生的一种锁。
执行alter table这类DDL语句的时候会加MDL写锁;
执行select、insert、update、delete语句时候,会加MDL读锁;
所以alter和crud语句之间会阻塞的;
关于元数据锁,有以下的知识点需要注意:
1、 我们可以在performance_schema中的表metadata_locks中查看元数据的锁定信息。
2、元数据锁设计一些开销,随着查询量的增加而增加,当多个查询尝试访问相同的对象时,元数据锁的争用情况就会增加。
3、如果我们的DML语句中,存在多个表,那么他们获取锁的顺序是按照语句中的顺序来的。语句中会一个一个的获取表的元数据锁,并在这个过程中执行死锁检测。
4、DDL语句中,一般是通过按照名称顺序来获取显示命名的表上的元数据锁,从而减少并发的DDL语句。(这一句可能不好理解,后面有例子)
5、如果我们的语句中包含外键,则外键相关的表也会被当前的锁定。
下面看几个元数据锁的例子,默认隔离级别是RR:
可以看到,会话1中开启事务,没有提交,此时在会话2中进行drop table的操作,再在会话1上查看连接,可以看到waiting for table metadata lock的字样,就是说明会话2在等待元数据锁。如果没有元数据锁的保护,那么我们可能在事务进行的过程中就会发现指定的表突然不存在了,这是我们不想看到的。
上面第4点中,说明了按照表名称来获取元数据锁,我们举例子如下:
rename table tbla to tbld,tblc to tbla;
rename table tbla to tblb,tblc to tbla;
上面两个语句,第一句获取元数据的顺序是tbla、tbld、tblc,因为tbld的表名顺序在tblc之前,第二句中获取元数据锁的顺序是tbla、tblb、tblc因为tblb的顺序在tblc的前面。不同的获取锁的顺序,会导致最终的执行结果有可能不相同,这一点需要特别注意。
为了确保事务可序列化,服务器不允许一个会话对在另一会话中未完成的显式或隐式启动的事务中使用的表执行DDL语句,服务器通过获取事务中使用的表上的元数据锁并将这些锁的释放推迟到事务结束之前来实现,表上的元数据锁可防止更改表的结构。
MDL锁和innodb存储引擎层面的IS和IX这种意向锁的区别是MDL锁是服务器中实现的,而不是在插件层面。除此之外,MDL锁可以实现全局锁、库级别的锁以及表空间级别的锁,这是插件式存储引擎锁不能实现的。按照MDL锁中级别进行分类,MDL锁可以分为以下几类:
-
属性 含义 范围/对象
-
GLOBAL 全局锁 范围 Waiting for global read lock
-
COMMIT 提交保护锁 范围 Waiting for commit lock
-
SCHEMA 库锁 对象 Waiting for schema metadata lock
-
TABLE 表锁 对象 Waiting for table metadata lock
-
FUNCTION 函数锁 对象 Waiting for stored function metadata lock
-
PROCEDURE 存储过程锁 对象 Waiting for stored procedure metadata lock
-
TRIGGER 触发器锁 对象 Waiting for trigger metadata lock
-
EVENT 事件锁 对象 Waiting for event metadata lock
下面我们看看在哪些情况下会发生元数据锁:
1、当事务中有DML操作的时候,事务没有提交的情况下,进行alter table或者drop table这种DML操作,会出现元数据锁等待,例如正在进行一个insert into ... select的操作;
2、当前在使用mysqldump进行备份,如果备份没有结束,则使用drop或者alter操作的时候,会用到元数据锁;
3、显示或者隐式开启事务后没有提交或者回滚,此时使用alter或者drop的操作室,会用到元数据锁(如同上面的例子)。
4、表上有失败的查询事务,例如查询了一个不存在的列,按道理是没有返回表中的数据的,但是这个时候,alter和drop操作依然会被堵住,像下面这样:
当出现元数据锁导致多个回话中的多个进程不可用的时候,通常会使用kill的方式来杀掉mysql中客户端进程id。
什么是死锁?怎么解决?怎么降低死锁?
什么是死锁?
死锁是指两个或者两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象。
虽然不能完全避免死锁,但可以使死锁的数量减至最少。将死锁减至最少可以增加事务的吞吐量并减少系统开销,因为只有很少的事务回滚,而回滚会取消事务执行的所有工作。由于死锁时回滚而由应用程序重新提交。
怎么解决?
解决死锁最有用最简单的方法是不要有等待,将任何等待都转化为回滚,并且事务重新开始。但是有可能影响并发性能。
1:超时回滚,即当两个事务互相等待时,当一个等待时间超过设置的某一阀值的时候,其中一个事务进行回滚,另一个事务就能继续进行。在InnoDB引擎中,参数innodb_lock_wait_time用来设置超时的时间。
2:wait-for-graph方法:跟超时回滚比起来,这是一种更加主动的死锁检测方式。InnoDB引擎也采用这种方式。这种方式一般要求数据库保存一下两种信息:锁的信息链表和事务等待链表。
通过这两条链表可以构造出一张图,而在这个图中若存在回路,就代表存在死锁,因此资源间互相发生等待。
在wait-for graph中,事务为图中的节点。而在图中,事务T1指向T2边的定义为:
事务T1等待事务T2所占用的资源
事务T1最终等待T2所占用的资源,也就是事务之间在等待相同的资源,而事务T1发送在事务T2的后面。
从wait-for graph图中我们可以发现存在回路(t1,t2),因此存在死锁。wait-for graph是主动监测死锁的机制,通常采用深度优先算法实现。一旦发现存在死锁,在两个事务中选择undo成本低的事务进行回滚
怎么降低死锁?
下列方法有助于最大限度地降低死锁:
(1)按同一顺序访问对象。
(2)避免事务中的用户交互。
(3)保持事务简短并在一个批处理中。
(4)使用低隔离级别。
按同一顺序访问对象
如果所有并发事务按同一顺序访问对象,则发生死锁的可能性会降低。例如,如果两个并发事务获得 Supplier 表上的锁,然后获得 Part 表上的锁,则在其中一个事务完成之前,另一个事务被阻塞在 Supplier 表上。第一个事务提交或回滚后,第二个事务继续进行。不发生死锁。将存储过程用于所有的数据修改可以标准化访问对象的顺序。
避免事务中的用户交互
避免编写包含用户交互的事务,因为运行没有用户交互的批处理的速度要远远快于用户手动响应查询的速度,例如答复应用程序请求参数的提示。例如,如果事务正在等待用户输入,而用户去吃午餐了或者甚至回家过周末了,则用户将此事务挂起使之不能完成。这样将降低系统的吞吐量,因为事务持有的任何锁只有在事务提交或回滚时才会释放。即使不出现死锁的情况,访问同一资源的其它事务也会被阻塞,等待该事务完成。
保持事务简短并在一个批处理中
在同一数据库中并发执行多个需要长时间运行的事务时通常发生死锁。事务运行时间越长,其持有排它锁或更新锁的时间也就越长,从而堵塞了其它活动并可能导致死锁。
保持事务在一个批处理中,可以最小化事务的网络通信往返量,减少完成事务可能的延迟并释放锁。
使用低隔离级别
确定事务是否能在更低的隔离级别上运行。执行提交读允许事务读取另一个事务已读取(未修改)的数据,而不必等待第一个事务完成。使用较低的隔离级别(例如提交读)而不使用较高的隔离级别(例如可串行读)可以缩短持有共享锁的时间,从而降低了锁定争夺。
分布式数据库
分布式数据库是什么?
分布式数据库是指利用高速计算机网络将物理上分散的多个数据存储单元连接起来组成一个逻辑上统一的数据库。分布式数据库的基本思想是将原来集中式数据库中的数据分散存储到多个通过网络连接的数据存储节点上,以获取更大的存储容量和更高的并发访问量。
分布式数据库特点
-
高可扩展性:分布式数据库必须具有高可扩展性,能够动态地增添存储节点以实现存储容量的线性扩展。
-
高并发性:分布式数据库必须及时响应大规模用户的读/写请求,能对海量数据进行随机读/写。
-
高可用性:分布式数据库必须提供容错机制,能够实现对数据的冗余备份,保证数据和服务的高度可靠性。
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhggfgjb
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
怎样阻止微信小程序自动打开
PHP中文网 06-13