sql索引优化,【数据库】第三章 事务、索引和SQL优化

 2023-09-25 阅读 20 评论 0

摘要:【数据库】第三章 事务、索引和SQL优化 文章目录【数据库】第三章 事务、索引和SQL优化一、事务1.原子性2.持久性3.隔离性4.一致性二、索引1.介绍2.分类3.底层实现4.语法三、SQL优化 一、事务 sql索引优化?事务是MySQL区别于NoSQL的重要特征,是保证关系型数据库数据一

【数据库】第三章 事务、索引和SQL优化

文章目录

  • 【数据库】第三章 事务、索引和SQL优化
  • 一、事务
    • 1.原子性
    • 2.持久性
    • 3.隔离性
    • 4.一致性
  • 二、索引
    • 1.介绍
    • 2.分类
    • 3.底层实现
    • 4.语法
  • 三、SQL优化

一、事务

sql索引优化?事务是MySQL区别于NoSQL的重要特征,是保证关系型数据库数据一致性的关键技术。事务可看作是对数据库操作的基本执行单元,可能包含一个或者多个SQL语句。这些语句在执行时,要么都执行,要么都不执行

事务的执行主要包括两个操作:

  • 提交:commit,将事务执行结果写入数据库
  • 回滚:rollback,回滚所有已经执行的语句,返回修改之前的数据

MySQL事务包含四个特性:

  • 原子性(Atomicity) :语句要么全执行,要么全不执行,是事务最核心的特性,事务本身就是以原子性来定义的;实现主要基于 undo log 日志实现的
  • 持久性(Durability :保证事务提交后不会因为宕机等原因导致数据丢失;实现主要基于 redo log 日志
  • 隔离性(Isolation) :保证事务执行尽可能不受其他事务影响;InnoDB默认的隔离级别是 RR,RR 的实现主要基于锁机制、数据的隐藏列、undo log 和类 next-key lock 机制
  • 一致性(Consistency) :事务追求的最终目标,一致性的实现既需要数据库层面的保障,也需要应用层面的保障

1.原子性

数据库第二章答案,事务的原子性就如原子操作一般,表示事务不可再分,其中的操作要么都做,要么都不做;如果事务中一个 SQL 语句执行失败,则已执行的语句也必须回滚,数据库退回到事务前的状态。只有 0 和 1,没有其它值

事务的原子性表明事务就是一个整体,当事务无法成功执行的时候,需要将事务中已经执行过的语句全部回滚,使得数据库回归到最初未开始事务的状态

事务的原子性就是通过 undo log 日志进行实现的。当事务需要进行回滚时,InnoDB 引擎就会调用 undo log 日志进行 SQL 语句的撤销,实现数据的回滚

2.持久性

mysql数据库索引?事务的持久性是指当事务提交之后,数据库的改变就应该是永久性的,而不是暂时的。这也就是说,当事务提交之后,任何其它操作甚至是系统的宕机故障都不会对原来事务的执行结果产生影响

事务的持久性是通过InnoDB存储引擎中的redo log日志来实现的

3.隔离性

原子性和持久性是单个事务本身层面的性质,而隔离性是指事务之间应该保持的关系。隔离性要求不同事务之间的影响是互不干扰的,一个事务的操作与其它事务是相互隔离的

数据库怎么创建索引。由于事务可能并不只包含一条 SQL 语句,所以在事务的执行期间很有可能会有其它事务开始执行。因此多事务的并发性就要求事务之间的操作是相互隔离的。这一点跟多线程之间数据同步的概念有些类似

锁机制

事务之间的隔离,是通过锁机制实现的。当一个事务需要对数据库中的某行数据进行修改时,需要先给数据加锁;加了锁的数据,其它事务是不运行操作的,只能等待当前事务提交或回滚将锁释放

数据库主键。锁机制并不是一个陌生的概念,在许多场景中都会利用到不同实现的锁对数据进行保护和同步。而在 MySQL 中,根据不同的划分标准,还可将锁分为不同的种类

按照粒度划分:行锁、表锁、页锁
按照使用方式划分:共享锁、排它锁
按照思想划分:悲观锁、乐观锁

并发读写问题

在并发情况下,MySQL的同时读写可能会导致三类问题,脏读、不可重复读和幻读

脏读:当前事务中读到其他事务未提交的数据,也就是脏数据
在这里插入图片描述

以上图为例,事务A在读取文章的阅读量时,读取到了事务B为提交的数据。如果事务B最后没有顺利提交,导致事务回滚,那么实际上阅读量并没有修改成功,而事务A却是读到的修改后的值,显然不合情理

不可重复读:在事务A中先后两次读取同一个数据,但是两次读取的结果不一样。脏读与不可重复读的区别在于:前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据
在这里插入图片描述
以上图为例,事务A在先后读取文章阅读量的数据时,结果却不一样。说明事务A在执行的过程中,阅读量的值被其它事务给修改了。这样使得数据的查询结果不再可靠,同样也不合实际

幻读:在事务A中按照某个条件先后两次查询数据库,两次查询结果的行数不同,这种现象称为幻读。不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了
在这里插入图片描述
以上图为例,当对0<阅读量<100的文章进行查询时,先查到了一个结果,后来查询到了两个结果。这表明同一个事务的查询结果数不一,行数不一致。这样的问题使得在根据某些条件对数据筛选的时候,前后筛选结果不具有可靠性

隔离级别

根据上面这三种问题,产生了四种隔离级别,表明数据库不同程度的隔离性质
在这里插入图片描述
在实际的数据库设计中,隔离级别越高,导致数据库的并发效率会越低;而隔离级别太低,又会导致数据库在读写过程中会遇到各种乱七八糟的问题
因此在大多数数据库系统中,默认的隔离级别时读已提交(如Oracle)或者可重复读 RR(MySQL的InnoDB引擎)

  • 读未提交: 安全性最差,可能发生并发数据问题,性能最好
  • 读已提交:Oracle默认的隔离级别
  • 可重复读:MySQL默认的隔离级别,安全性较好,性能一般
  • 可串行化:表级锁,读写都加锁,效率低下,安全性高,,能并发

MVCC

MVCC(Multi-Version Concurrency Control,即多版本的并发控制协议)就是用来实现上面的第三个隔离级别,可重复读RR

MVCC 的特点就是在同一时刻,不同事务可以读取到不同版本的数据,从而可以解决脏读和不可重复读的问题

MVCC 实际上就是通过数据的隐藏列和回滚日志(undo log),实现多个版本数据的共存。这样的好处是,使用 MVCC 进行读数据的时候,不用加锁,从而避免了同时读写的冲突

在实现 MVCC 时,每一行的数据中会额外保存几个隐藏的列,比如当前行创建时的版本号和删除时间和指向 undo log 的回滚指针。这里的版本号并不是实际的时间值,而是系统版本号。每开始新的事务,系统版本号都会自动递增。事务开始时的系统版本号会作为事务的版本号,用来和查询每行记录的版本号进行比较

每个事务又有自己的版本号,这样事务内执行数据操作时,就通过版本号的比较来达到数据版本控制的目的

另外,InnoDB 实现的隔离级别 RR 时可以避免幻读现象的,这是通过 next-key lock 机制实现的

next-key lock 实际上就是行锁的一种,只不过它不只是会锁住当前行记录的本身,还会锁定一个范围。比如上面幻读的例子,开始查询 0<阅读量<100 的文章时,只查到了一个结果。next-key lock 会将查询出的这一行进行锁定,同时还会对 0<阅读量<100 这个范围进行加锁,这实际上是一种间隙锁(封锁索引记录中的间隔)。间隙锁能够防止其他事务在这个间隙修改或者插入记录。这样一来,就保证了在 0<阅读量<100 这个间隙中,只存在原来的一行数据,从而避免了幻读

4.一致性

一致性是指事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态

一致性是事务追求的最终目标,原子性、持久性和隔离性,实际上都是为了保证数据库状态的一致性而存在的

二、索引

1.介绍

索引是一种排好序的快速查找的数据结构,它帮助数据库高效的进行数据的检索。在数据之外,数据库系统还维护着满足特定查找算法的数据结构(需要额外的存储空间),这些数据结构以某种方式指向数据,这样就可以在这些数据结构上实现高效的查找算法。这种数据结构就叫做索引

一般来说索引本身也很大,不可能全部存储在内存中,因此往往以索引文件的形式存放在磁盘中

什么时候需要创建索引

表的数据量过大,而且业务中需要频繁查询,查询速度慢时。

使用索引的条件

创建索引的列必须是 where 子句的条件,否则索引就起不到作用

条件可以是 < 或 <= 或 = 或 > 或 >= 或 BETWEEN 或 IN ,但是不要用 not in 和 <>

使用 LIKE 时,条件可以是 “xxx%”,但不可以是"%xxx"

索引的缺点

索引对查询速度可以大幅提升,但不推荐滥用。创建索引后,对表数据进行 INSERT、UPDATE 和 DELETE 时,同样会对索引文件进行修改,从而影响性能

建立索引后还要建立索引表,相当于建了两份表。此外,建立索引还会占用磁盘空间

2.分类

  1. 从存储结构上来划分:BTree索引(B-Tree或B+Tree索引),Hash索引,full-index全文索引,R-Tree索引
  2. 从应用层次来分:普通索引,唯一索引,复合索引
  3. 根据数据的物理顺序与键值的逻辑(索引)顺序关系:聚集索引,非聚集索引

1 中所描述的是索引存储时保存的形式,2 是索引使用过程中进行的分类,两者是不同层次上的划分。不过平时讲的索引类型一般是指在应用层次的划分

普通索引:即一个索引只包含单个列,一个表可以有多个单列索引
唯一索引:索引列的值必须唯一,但允许有空值
复合索引:即一个索引包含多个列
聚簇索引(聚集索引):并不是一种单独的索引类型,而是一种数据存储方式。具体细节取决于不同的实现,InnoDB的聚簇索引其实就是在同一个结构中保存了B-Tree索引(技术上来说是B+Tree)和数据行
非聚簇索引:不是聚簇索引,就是非聚簇索引

3.底层实现

MySQL 默认存储引擎 innodb 只显式支持 B-Tree( 从技术上来说是 B+Tree) 索引,对于频繁访问的表,innodb 会透明建立自适应 hash 索引,即在 B 树索引基础上建立 hash 索引,可以显著提高查找效率,对于客户端是透明的,不可控制的,隐式的

4.语法

创建索引

create index index_name on 表名(字段名(length));

创建复合索引

create index index_name on 表名(字段1,字段2);
#当我们创建一个复合索引的时候,如(k1.k2.k3),相当于创建了 (k1)、(k1,k2) 和 (k1,k2,k3) 三个索引,这就是最左匹配原则,也称为最左特性

删除索引

drop index index_name on 表名;

显示索引

show index from 表名;

观察索引的执行

EXPLAIN
SELECT语句

三、SQL优化

SQL的执行顺序

(1) FROM [left_table] 选择表
(2) ON <join_condition> 连接条件
(3) <join_type> JOIN <right_table> 连接
(4) WHERE <where_condition> 条件过滤
(5) GROUP BY <group_by_list> 分组
(6) AGG_FUNC(column or expression),… 聚合
(7) HAVING <having_condition> 分组过滤
(8) SELECT (9) DISTINCT column,… 选择字段、去重
(9) ORDER BY <order_by_list> 排序
(10) LIMIT count OFFSET count; 分页

1、对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引

2、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num is null

最好不要给数据留 NULL,尽可能地使用 NOT NULL 填充数据库

备注、描述、评论之类的可以设置为 NULL,其他的最好不要使用 NULL

不要认为 NULL 不需要空间,比如:char(100),在字段建立时,空间就固定了,不管是否插入值(NULL 也包含在内),都是占用 100 个字符的空间的,如果是 varchar 这样的变长字段,NULL 不占用空间

解决方案:可以在 num 上设置默认值为 0,确保表中 num 列没有 NULL 值,然后这样查询:

select id from t where num = 0

3、应尽量避免在 where 子句中使用 != 或 <> ,否则引擎会放弃使用索引,而进行全表扫描

4、应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num = 10 or Name = “admin”

可以这样查询:

select id from t where num = 10
union all
select id from t where Name = “admin”

5、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

很多时候用 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)

6、下面的查询也将导致全表扫描:

select id from t where name like “%abc%”

若要提高效率,可以考虑全文检索

7、如果在 where 子句中使用参数,也会导致全表扫描。因为 SQL 只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时,它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。比如下面的语句将进行全表扫描:

select id from t where num = @num

可以改为强制查询使用索引:

select id from t with(index(索引名)) where num = @num

8、应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num/2 = 100

应改为:

select id from t where num = 100*2

9、应尽量避免在 where 子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where substring(name,1,3) = “abc”
select id from t where datediff(day,createdate,“2005-11-30”) = 0

应改为:

select id from t where name like “abc%”
select id from t where createdate >= “2005-11-30” and createdate < “2005-12-1”

10、不要在 where 子句中的 “=” 左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引

11、在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应该尽可能地让字段顺序与索引顺序相一致

12、不要写一些没有意义的查询,比如,如果需要生成一个空表结构:

select col1,col2 into #t from t where 1 = 0

这类代码不会返回任何结果集,但是会消耗系统资源,应改成:

create table #t(…)

13、Update 语句,如果只更改一两个字段,不要 Update 全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志

14、对于多张大数据量(超过 100条)的表 JOIN,要先分页再 JOIN,否则逻辑读会很高,性能很差

15、下面这样不带任何条件的 count 会引起全表扫描,并且没有任何业务意义,是一定要杜绝的

select count(*) from table;

16、索引并不是越多越好,索引固然可以提高相应的 select 效率,但同时也降低了 insert 和 update 的效率,因为 insert 或 update 时有可能会重新建立索引。所以怎样建索引需要慎重考虑,视情况而定。一个表的索引数最好不要超过 6 个,若太多则应考虑一下,在那些不常使用到的字段上建立的索引是否有必要

17、应该尽可能地避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列的值改变,将导致整个表记录的顺序发生调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应该将该索引建为 clustered 索引

18、尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并且会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言,只需要比较一次就够了

19、尽可能地使用 varchar/nvarchar 代替 char/nchar,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小地字段内搜索效率显然要高些

20、任何地方都不要用 select * from t,应该用具体的字段列表代替 * ,不要返回用不到的任何字段

21、尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)

22、避免频繁创建和删除临时表,以减少系统表资源的消耗。临时表并不是不可使用,适当地使用它们可以使某些例程更加有效。比如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表

23、在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log,以提高速度。如果数据量不大,为了缓和系统表的资源,应先 create table,然后 insert

24、如果使用到了临时表,在存储过程的最后务必将所有的临时表显示删除,先 truncate table,然后 drop table,这样可以避免系统表的较长时间锁定

25、尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过 1 万行,那么就应该考虑改写

26、使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效

27、与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括 ”合计“ 的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好

28、在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息

29、尽量避免大事务操作,提高系统并发能力

30、尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/4/95678.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息