文章目录
- MySQL进阶
- MySQL进阶思维导图
-
- 相关目录及配置文件my.cnf介绍
-
- 相关目录介绍
- my.cnf
- 执行计划
-
- 什么执行计划
- 执行计划的使用方式
- 字段含义说明
- 索引
-
- 概念
- 优点
- 缺点
- 语法
- 分类
- 数据结构
- MyISAM与InnoDB的索引区别
- 总结聚簇索引与非聚簇索引
- 面试常问的技术名词
- 索引匹配方式
- 索引优化
-
- 索引优化一般也都是指的针对一些慢查询的优化,需要针对实际的业务,结合索引的特性进行优化;不能一味的去创建索引,最重要的是懂得灵活的去使用MySQL的执行计划
- 优化细节
- 索引监控
-
-
- MySQL查询SQL执行时间
- 设置慢查询监控
- 查看索引的使用情况
-
- 哪些情况需要创建索引
- 哪些情况不适合建索引
- 数据库事务
-
- 什么是数据库事务
- 事务的四大特性(ACID)
- 事务的并发问题
- 隔离级别
- 锁
-
- 为什么要有锁?
- 悲观锁/乐观锁得理解
- MySQL中的锁
- MyISAM表锁两种模式
- InnoDB行锁两种类型
- InnoDB间隙锁
- MySql分表(针对大数据量查询缓慢的情况)
-
-
- 相关文章
-
MySQL进阶 前面入门之后,下面继续进阶
文章图片
链接:https://pan.baidu.com/s/18KDKZxnhmWH9Ebpdhy6DlA 提取码:7665
上面网盘地址自行取图“Mysql进阶思维导图.png”;下面是markdown转换后的格式
MySQL进阶思维导图 相关目录及配置文件my.cnf介绍
文章图片
相关目录介绍
- bin - 存放编译好的工具
- data - 存放数据库数据位置
- docs - 存放文档的地方,比如changelog
- include - 存放客户端开发的库
- lib - 这里就相当于mysql-shared,里面的包含libmysqlclient.so*
- log - mysql日志目录
- man - mysqld命令帮助说明文档
- mysql.sock - mysql的套接字文件
- share - 存放几个初始表文件和每种语言的errmsg.sys错误日志
- support-files - 服务端操作文件,譬如 mysql.server
这里是基于MySQL 5.7.28 安装目录的大致介绍,非DBA可以不用太详细去了解,有兴趣的可以自行去了解;其中/data、/log、/mysql。socket目录及文件是在安装是自行进行配置的;可以修改
my.cnf
- 加载顺序
- mysql --help|grep my.cnf -
查看mysql加载配置文件顺序
文章图片
**还有个默认的指定文件,所以整体顺序为:/etc/my.cnf > /etc/mysql/my.cnf > /usr/local/mysql/etc/my.cnf > --defaults-extra-file > ~/.my.cnf;假设4个配置文件都存在,同时使用–defaults-extra-file指定了参数文件,如果这时有一个 "参数变量"在5个配置文件中都出现了,那么后面的配置文件中的参数变量值 会 覆盖 前面配置文件中的参数变量值,就是说会使用~/.my.cnf中设置的值。 **
- mysql --help|grep my.cnf -
- 读取路径修改
- 这里需要了解一下mysql启动服务的另一种方式就是mysqld_safe,他与mysqld区别就是mysqld_safe脚本会在启动MySQL服务器后继续监控其运行情况,并在其死机时重新启动它。并且mysqld_safe带有两个参数可以需改my.cnf的路径
文章图片
--defaults-extra-file=path
: 除了通用选项文件所读取的选项文件名。如果给出,必须首选该选项。--defaults-file=path
: 读取的代替通用选项文件的选项文件名。如果给出,必须首选该选项
- 这里需要了解一下mysql启动服务的另一种方式就是mysqld_safe,他与mysqld区别就是mysqld_safe脚本会在启动MySQL服务器后继续监控其运行情况,并在其死机时重新启动它。并且mysqld_safe带有两个参数可以需改my.cnf的路径
- 部分相关配置参数
首先需要了解选项组是什么,选项组就是my.cnf配置中用[]括起来的,譬如[client]、[mysqld]、[mysqld_safe]、……;这里只介绍部分;每个选项组对应的优先级可能不同,具体的自行百度吧
[client]
客户端设置,即客户端默认的连接参数;可以理解为连接mysql时替换-u, -p,等相关参数
[client]
host=127.0.0.1
user=root
password=[yourpass]
#为了省去每次链接数据库都输入主机地址,用户,密码可以配置上面三个参数如果你的MYSQL用户名和UNIX的登陆名相同,可以不写user行port = 3307#默认连接端口
socket = /data/mysqldata/3307/mysql.sock#用于本地连接的socket套接字
default-character-set = utf8mb4#编码
[mysqld]
服务端基本设置
[mysqld]
default-storage-engine=InnoDB#默认存储引擎INNODB
group_concat_max_len =99999#GROUP_CONCAT长度
port = 3306#端口号
socket = /usr/local/mysql/mysql.sock#套接字文件这里要注意:有时候重启mysql会提示/tmp/mysql.sock不存在
pid-file = /usr/local/mysql/mysqld.pid#pid写入文件位置
datadir = /home/data/mysql/data#数据库文件位置
open_files_limit = 10240#控制文件打开的个数
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES#SQL模式,允许一些非法操作;
相关参数值含义如下
#ONLY_FULL_GROUP_BY:对于GROUP BY聚合操作,如果在SELECT中的列,没有在GROUP BY中出现,那么将认为这个SQL是不合法的,因为列不在GROUP BY从句中
#STRICT_TRANS_TABLES:在该模式下,如果一个值不能插入到一个事务表中,则中断当前的操作,对非事务表不做任何限制
#NO_ZERO_IN_DATE:在严格模式,不接受月或日部分为0的日期。如果使用IGNORE选项,我们为类似的日期插入'0000-00-00'。在非严格模式,可以接受该日期,但会生成警告。
#NO_ZERO_DATE:在严格模式,不要将 '0000-00-00'做为合法日期。你仍然可以用IGNORE选项插入零日期。在非严格模式,可以接受该日期,但会生成警告
#ERROR_FOR_DIVISION_BY_ZERO:在严格模式,在INSERT或UPDATE过程中,如果被零除(或MOD(X,0)),则产生错误(否则为警告)。如果未给出该模式,被零除时MySQL返回NULL。如果用到INSERT IGNORE或UPDATE IGNORE中,MySQL生成被零除警告,但操作结果为NULL。
#NO_AUTO_CREATE_USER:防止GRANT自动创建新用户,除非还指定了密码。
#NO_ENGINE_SUBSTITUTION:如果需要的存储引擎被禁用或未编译,那么抛出错误。不设置此值时,用默认的存储引擎替代,并抛出一个异常。skip-external-locking
#当外部锁定(external-locking)起作用时,每个进程若要访问数据表,
#则必须等待之前的进程完成操作并解除锁定。由于服务器访问数据表时经常需要等待解锁,
#因此在单服务器环境下external locking会让MySQL性能下降。
#所以在很多Linux发行版的源中,MySQL配置文件中默认使用了skip-external-locking来避免external locking。skip-name-resolve#跳过DNS反向解析
explicit_defaults_for_timestamp#关闭TIMESTAMP类型默认值
skip-character-set-client-handshake#不受client字符集影响,保证sever端字符集
init-connect='SET NAMES utf8'#初始连接字符集UTF8
character-set-server=utf8#默认数据库字符集
query_cache_type = 1#查询缓存0,1,2,分别代表了off、on、demand
connect_timeout = 20#单位秒,握手时间超过connect_timeout,连接请求将会被拒绝
slave_net_timeout = 30
#设置在多少秒没收到主库传来的Binary Logs events之后,从库认为网络超时,Slave IO线程会重新连接主库。
#该参数的默认值是3600s ,然而时间太久会造成数据库延迟或者主备库直接的链接异常不能及时发现。
#将 slave_net_timeout 设得很短会造成 Master 没有数据更新时频繁重连。一般线上设置为5slog-slave-updates=1
#这个参数用来配置从服务器的更新是否写入二进制日志,这个选项默认是不打开的,
#但是,如果这个从服务器B是服务器A的从服务器,同时还作为服务器C的主服务器,那么就需要开发这个选项,
#这样它的从服务器C才能获得它的二进制日志进行同步操作replicate-same-server-id=0#用于slave服务器,io线程会把server id与自己相同的event写入日志,与log-slave-updates选项冲突
server_id=10112879101#作为MASTER主服务器
log-bin =/home/data/mysql/binlog/mysql-bin.log#打开二进制日志功能.
#在复制(replication)配置中,作为MASTER主服务器必须打开此项
#如果你需要从你最后的备份中做基于时间点的恢复,你也同样需要二进制日志relay-log=mysql-relay-bin#relay-log日志
master-info-repository=TABLE#master-info-repository打开以启用崩溃安全的二进制日志(在事务表而不是平面文件中存储信息)
relay-log-info-repository=TABLE#relay-log-info-repository打开以启用崩溃安全的从服务器功能(在事务表而不是平面文件中存储信息)
binlog-ignore-db=mysql # No sync databases
binlog-ignore-db=test # No sync databases
binlog-ignore-db=information_schema # No sync databases
binlog-ignore-db=performance_schema # No sync databases
#不写入binlog二进制日志中的数据库binlog-do-db=business_db
binlog-do-db=user_db
binlog-do-db=plocc_system
#写入binlog二进制日志中数据库expire-logs-days=15
max_binlog_size = 1073741824 # Bin logs size ( 1G )
#清理binlogsync_binlog = 1000#使binlog在每1000次binlog写入后与硬盘同步
replicate-do-db=business_db
replicate-do-db=user_db
replicate-do-db=plocc_system
#指定只复制哪个库的数据event_scheduler=1#开启事件调度器Event Scheduler
back_log = 500
#MySQL能暂存的连接数量。当主要MySQL线程在一个很短时间内得到非常多的连接请求,这就起作用。
#如果MySQL的连接数据达到max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,
#该堆栈的数量即back_log,如果等待连接的数量超过back_log,将不被授予连接资源
#如果系统在短时间内有很多连接,则需要增大该参数的值,该参数值指定到来的TCP/IP连接的监听队列的大小。默认值50。max_connections = 6000#MySQL允许最大的进程连接数,如果经常出现Too Many Connections的错误提示,则需要增大此值。
max_user_connection = 3000#每个用户的最大的进程连接数
max_connect_errors = 6000
#每个客户端连接请求异常中断的最大次数,如果达到了此限制.
#这个客户端将会被MySQL服务阻止,直到执行了”FLUSH HOSTS” 或者服务重启
#非法的密码以及其他在链接时的错误会增加此值.
#查看 “Aborted_connects” 状态来获取全局计数器table_cache = 614
#表调整缓冲区大小。
#table_cache 参数设置表高速缓存的数目。每个连接进来,都会至少打开一个表缓存。
#因此,table_cache 的大小应与 max_connections 的设置有关。例如,对于 200 个并行运行的连接,应该让表的缓存至少有 200 × N ,这里 N 是应用可以执行的查询的一个联接中表的最大数量。此外,还需要为临时表和文件保留一些额外的文件描述符。
#当Mysql访问一个表时,如果该表在缓存中已经被打开,则可以直接访问缓存;如果还没有被缓存但是在 Mysql 表缓冲区中还有空间,那么这个表就被打开并放入表缓冲区;如果表缓存满了,则会按照一定的规则将当前未用的表释放,或者临时扩大表缓存来存放,使用表缓存的好处是可以更快速地访问表中的内容。
#执行 flush tables 会清空缓存的内容。
#一般来说,可以通过查看数据库运行峰值时间的状态值 Open_tables 和 Opened_tables ,判断是否需要增加 table_cache 的值(其中 open_tables 是当前打开的表的数量, Opened_tables 则是已经打开的表的数量)。
#即如果open_tables接近table_cache的时候,并且Opened_tables这个值在逐步增加,那就要考虑增加这个#值的大小了。还有就是Table_locks_waited比较高的时候,也需要增加table_cache。table_open_cache = 2048#表描述符缓存大小,可减少文件打开/关闭次数
max_allowed_packet = 64M#设置在网络传输中一次消息传输量的最大值。系统默认值 为1MB,最大值是1GB,必须设置1024的倍数。当与大的BLOB字段一起工作时相当必要
binlog_cache_size = 1M
# 在一个事务中binlog为了记录SQL状态所持有的cache大小
# 如果你经常使用大的,多声明的事务,你可以增加此值来获取更大的性能.
# 所有从事务来的状态都将被缓冲在binlog缓冲中然后在提交后一次性写入到binlog中
# 如果事务比此值大, 会使用磁盘上的临时文件来替代.
# 此缓冲在每个连接的事务第一次更新状态时被创建max_heap_table_size = 256M#独立的内存表所允许的最大容量.此选项为了防止意外创建一个超大的内存表导致用尽所有的内存资源
sort_buffer_size = 8M
#Sort_Buffer_Size被用来处理类似ORDER BY以及GROUP BY队列所引起的排序,每一个要做排序的请求,都会分到一个sort_buffer_size大的缓存
#Sort_Buffer_Size 是一个connection级参数,在每个connection(session)第一次需要使用这个buffer的时候,一次性分配设置的内存。
#Sort_Buffer_Size 并不是越大越好,由于是connection级的参数,过大的设置+高并发可能会耗尽系统内存资源。例如:500个连接将会消耗 500*sort_buffer_size(8M)=4G内存
#如果超过Sort_Buffer_Size设置的大小,MySQL会将数据写入磁盘来完成排序,导致效率降低。
#属重点优化参数join_buffer_size = 8M
#用于表间关联缓存的大小,和sort_buffer_size一样,该参数对应的分配内存也是每个连接独享。
#大部分表关联都比较影响查询性能,
#所以将此值设大能够减轻性能影响。
#通过 “Select_full_join” 状态变量查看表关联的数量thread_cache_size = 128
#thread_cache_size表示可以重新利用保存在缓存中线程的数量,当断开连接时如果缓存中还有空间,那么客户端的线程将被放到缓存中,
#如果线程重新被请求,那么请求将从缓存中读取,如果缓存中是空的或者是新的请求,那么这个线程将被重新创建,如果有很多新的线程,减少线程创建的开销
#可以通过比较 Connections 和 Threads_created 状态变量,来查看thread_cache_size的设置是否起作用。
#设置规则:1GB 内存配置为8,2GB配置为16,3GB配置为32,4GB或更高内存,可配置更大。thread_concurrency = 8
#此值表示允许应用程序在同一时间运行的线程的数量.
#设置thread_concurrency的值的正确与否,对mysql的性能影响很大, 在多个cpu(或多核)的情况下,错误设置了thread_concurrency的值, 会导致mysql不能充分利用多cpu(或多核), 出现同一时刻只能一个cpu(或核)在工作的情况。
#thread_concurrency应设为CPU核数的2倍
#属重点优化参数query_cache_size = 64M
#此值用来缓冲 SELECT 的结果并且在下一次同样查询的时候不再执行直接返回结果,如果你有大量的相同的查询并且很少修改表,那么query_cache_size可以极大的提高数据库性能,
#需要注意的是:有时候数据库出现了性能问题,大家就习惯的认为把这个值调大就行了。然而,这个参数加大后也引发了一系列问题。
#我们首先分析一下 query_cache_size的工作原理:一个SELECT查询在DB中工作后,DB会把该语句缓存下来,当同样的一个SQL再次来到DB里调用时,DB在该表没发生变化的情况下把结果从缓存中返回给Client。
#这里有一个关建点,就是DB在利用Query_cache工作时,要求该语句涉及的表在这段时间内没有发生变更。那如果该表在发生变更时,Query_cache里的数据又怎么处理呢?
#首先要把Query_cache和该表相关的语句全部置为失效,然后在写入更新。那么如果Query_cache非常大,该表的查询结构又比较多,查询语句失效也慢,一个更新或是Insert就会很慢,这样看到的就是Update或是Insert怎么这么慢了。
#所以在数据库写入量或是更新量也比较大的系统,该参数不适合分配过大。而且在高并发,写入量大的系统,建议把该功能禁掉。
#重点优化参数query_cache_limit = 2M
#指定单个查询能够使用的缓冲区大小,只有小于此设定值的结果才会被缓冲
#此设置用来保护查询缓冲,防止极大的结果集将其他所有的查询结果都覆盖
#缺省为1Mft_min_word_len = 4
#被全文检索索引的最小的字长.
#你也许希望减少它,如果你需要搜索更短字的时候.
#注意在你修改此值之后,
#你需要重建你的 FULLTEXT 索引thread_stack = 192K
#设置MYSQL线程使用的堆大小,此容量的内存在每次连接时被预留.
#MySQL 本身常不会需要超过64K的内存
#如果你使用你自己的需要大量堆的UDF函数
#或者你的操作系统对于某些操作需要更多的堆,
#你也许需要将其设置的更高一点.transaction_isolation = READ-COMMITTED
#设定默认的事务隔离级别.可用的级别如下:
#READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLEtmp_table_size = 256M
#此值表示内存中临时表的最大大小,超过限值后就往硬盘写
#此限制是针对单个表的,而不是总和
#注意:
#1. max_heap_table_size 比 tmp_table_size 小时,则系统会把 max_heap_table_size 的值作为最大的内存临时表的上限。这样可达到提高联接查询速度的效果,建议尽量优化查询,要确保查询过程中生成的临时表在内存中,避免临时表过大导致生成基于硬盘的MyISAM表。
#2. 通过show global status like '%created_tmp%' 查询:Created_tmp_disk_tables和Created_tmp_tables的值,Created_tmp_disk_tables / Created_tmp_tables 值越小越好binlog_format=mixed#binlog日志类型;mixed:混合型
slow_query_log#开启慢查询日志
log_output = FILE#文件格式
long_query_time = 0.5
#所有的使用了比这个时间(以秒为单位)更多的查询会被认为是慢速查询.
#不要在这里使用”0″, 否则会导致所有的查询,甚至非常快的查询页被记录下来(由于MySQL 目前时间的精确度只能达到秒的级别).slow_query_log_file=/usr/local/mysql/mysqld_slow.log#慢查询日志位置
总结
mysql的优化其实也主要是对配置文件中参数的优化,具体以实际情况而定;还有其他一些选项组的配置这块就不做阐述了,非DBA能了解这么多足以
执行计划 什么执行计划
- SQL执行计划,就是一条SQL语句,在数据库中实际执行的时候,一步步的分别都做了什么。就是我们用EXPLAIN分析一条SQL语句时展示出来的那些信息;了解SQL执行计划的意义就在于我们可以通过执行计划更加清晰的认识到这一条语句,分为了哪几步,有没有用到索引,是否有一些可优化的地方等。
- EXPLAIN -
譬如:explain select * from students where id = 1;
文章图片
-
id
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序。三种情况:
- 1,id相同,执行顺序由上至下;
- 2,id不同,如果是子查询,id的序列号会递增,id值越大优先级越高,越先被执行;
- 3,id不同,同时存在(衍生=DERIVED)
-
select_type
查询类型,主要用于区别普通查询、联合查询、子查询等的复杂查询。常见类型又六种
- SIMPLE : 简单的select查询,查询中不包含子查询或者UNION
- PRIMARY : 查询中若包含任何复杂的子部分,最外层查询则被标记为PRIMARY
- SUBQUERY : 在select或where列表中包含了子查询
- DERIVED : 在from列表中包含的子查询被标记为DERIVED(衍生)MySQL会递归执行这些子查询,把结果放在临时表里。
- UNION : 若第二个select出现在union之后,则被标记为union;若union包含在from字句的子查询中,外层select将被标记为:DERIVED
- UNION RESULYT : 从union表获取结果的select
-
table
显示这一行的数据是关于哪张表
-
partitions
记录将与查询匹配的分区。对于非分区表,该值为NULL。
-
type
访问类型排列,共有八种类型(还有一个NULL)
- system : 表只有一行记录(等于系统表),这是const类型的特列,平时不会出现,这个可以忽略不计。
- const : 表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快,如将主键置于where列表中,Mysql就能将该查询转换为一个常量。
- eq_ref : 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。
- ref : 非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。
- range : 只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是在你的where语句中出现了between、<、>、in等的查询,这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引。
- index : Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小。(也就是说虽然all和Index都是读全表,但index是从索引中读取的,而all是从硬盘中读的)
- ALL : Full Table Scan,将遍历全表以找到匹配的行。
-
possible_keys
显示可能应用在这张表中的索引,一个或多个。查询设计到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。
-
key
实际使用的索引。如果为NULL,则没有使用索引;查询中若使用了覆盖索引(覆盖索引:指查询的字段个数及顺序刚好与复合索引一致),则该索引仅出现在key列表中。
-
key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引长度。在不损失精确性的情况下,长度越短越好。key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。
-
ref
显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
-
rows
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。
-
filtered
指示将按表条件筛选的表行的估计百分比。
最大值为100,这意味着不会对行进行过滤。
值从100开始减少表示过滤量增加。
-
Extra
包含不适合在其他列中显示但十分重要的额外信息。
- Using filesort : 说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取;Mysql中无法利用索引完成的排序操作称为“文件排序”(需要跟order by 排序字段与索引顺序一致)
- Using temporary : 使用了临时表保存中间结果,Mysql在对查询结果排序时使用临时表。常见于排序order by和分组查询group by。(j建议group by要么不建立索引,要么跟索引顺序一致)
- Using index : 表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错!如果同时出现Using where,表明索引被用来执行索引键值的查找;如果没有同时出现Using where,表明索引用来读取数据而非执行查找动作。 覆盖索引(Covering Index):一说为索引覆盖。理解方式一:就是select的数据列只用从索引中就能够取得,不必读取数据行,Mysql可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。 注意:如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select *,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。
- Using where : 表明使用了where过滤
- Using join buffer : 使用了连接缓存
- impossible where : where子句的值总是false,不能用来获取任何元组
- select tables optimizwd away : 在没有GROUP BY子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
- distinct : 优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作。
- 其他,还有很多因为很少遇到这里就不进行阐述
- 帮助MySql高效获取数据的数据结构。索引的本质是:索引是一种数据结构。“排好序的快速查找数据结构”
- 1,很大程度上减少服务器需要扫描的数据量
- 2,帮助服务器通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗
- 3,将随机io变成顺序io
- 1,实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,索引以索引文件的形式存储在磁盘上所以索引列也是要占用空间的
- 2,虽然索引大大提高了查询速度,同时也会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySql不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。
- 3,索引只是提高效率的一个因素,如果你的MySql有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询。
- 创建
- create [unique] index 索引名on 表名(列名1[,列名2])
- alter 表名add [unique] index [索引名] on (列名1[,列名2])
- 查看
- show index from 表名\G
- 删除
- drop index [索引名] on 表名
- 主键索引
- alter table 表名 add primary key(列名1[,列名2]) #该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL
- 唯一索引
- alter table 表名 add unique 索引名(列名1[,列名2]) #这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能出现多次)
- 普通索引
- alter table 表名 add index 索引名(列名1[,列名2]) #添加普通索引,索引值可出现多次
- 全文索引
- alter table 表名 add fulltext 索引名(列名1[,列名2]) #该语句指定了索引为fulltext,用于全文索引
- 组合索引
- 组合索引,个人认为不在分类这块,组合索引就是多列组合成一个索引;理解就行。当包含多个列作为索引,需要注意的是正确的顺序依赖于该索引的查询,同时需要考虑如何更好的满足排序和分组的需要
- 案例,建立组合索引a,b,c;不同SQL语句使用索引情况
文章图片
- B+ Tree索引
参照文章理解 : https://blog.csdn.net/yeming_666/article/details/105975444 - 哈希索引(简单了解,问的不多)
- 总结
- 1,基于哈希表的实现,只有精确匹配索引所有列的查询才有效;
- 2,在mysql中,只有memory的存储引擎显式支持哈希索引;
- 3,哈希索引自身只需存储对应的hash值,所以索引的结构十分紧凑,这让哈希索引查找的速度非常快
- 限制
- 1、哈希索引只包含哈希值和行指针,而不存储字段值,索引不能使用索引中的值来避免读取行
- 2、哈希索引数据并不是按照索引值顺序存储的,所以无法进行排序
- 3、哈希索引不支持部分列匹配查找,哈希索引是使用索引列的全部内容来计算哈希值
- 4、哈希索引支持等值比较查询,也不支持任何范围查询
- 5、访问哈希索引的数据非常快,除非有很多哈希冲突,当出现哈希冲突的时候,存储引擎必须遍历链表中的所有行指针,逐行进行比较,直到找到所有符合条件的行
- 6、哈希冲突比较多的话,维护的代价也会很高
- 总结
- 这里的数据结构以B+树为准
- 假设有一张学生表students,建表语句如下。不同的搜索引擎修改对应的建表语句的引擎就好。
-- 学生表students表结构如下:
CREATE TABLE `students` (
`no` int NOT NULLAUTO_INCREMENTCOMMENT '学号',
`age` int NOT NULL DEFAULT '0' COMMENT '年龄',
`name` varchar(64) NOT NULL DEFAULT '' COMMENT '姓名',
PRIMARY KEY (`no`),
KEY `idx_age` (`age`)
) ENGINE=InnoDB/MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='学生信息表';
-- 初始化学生表数据
INSERT INTO `students`(`no`, `age`, `name`) VALUES (1, 20, '张三');
INSERT INTO `students`(`no`, `age`, `name`) VALUES (2, 23, '李四');
INSERT INTO `students`(`no`, `age`, `name`) VALUES (3, 25, '王五');
INSERT INTO `students`(`no`, `age`, `name`) VALUES (4, 32, '高六');
INSERT INTO `students`(`no`, `age`, `name`) VALUES (5, 25, '刘七');
INSERT INTO `students`(`no`, `age`, `name`) VALUES (6, 27, '朱八');
INSERT INTO `students`(`no`, `age`, `name`) VALUES (7, 28, '陈七');
INSERT INTO `students`(`no`, `age`, `name`) VALUES (8, 21, '赫赫');
INSERT INTO `students`(`no`, `age`, `name`) VALUES (9, 22, '一一');
-
MyISAM 索引实现
MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。
- 主键索引 - 以主键索引进行排序,key是唯一;结构如下:
文章图片
- 辅助索引 - 以非主键索引进行排序,key可以重复;结构如下
文章图片
- 主键索引 - 以主键索引进行排序,key是唯一;结构如下:
-
InnoDB索引实现
InnoDB引擎同样使用B+Tree作为索引结构,实现方式却完全不同。InnoDB表数据文件本身就是一个索引结构。
- 主键索引 - 以主键索引进行排序,key是唯一;主键索引树的叶节点data域保存了完整的数据记录。结构如下
文章图片
- 辅助索引 - 以非主键索引进行排序,key可以重复;辅助索引树的叶节点data域保存的是主键结构如下
文章图片
- 主键索引 - 以主键索引进行排序,key是唯一;主键索引树的叶节点data域保存了完整的数据记录。结构如下
- 【Mysql|MySQL5.7.28_03_一张图片带你进阶MySQL】总结:MyISAM索引与InnoDB索引的区别
- InnoDB 索引是聚簇索引(个人理解:数据挂载在索引下面),MyISAM 索引是非聚簇索引(个人理解:索引下挂载的是物理地址,指向数据)。
- InnoDB 的主键索引的叶子节点存储着行数据,因此主键索引非常高效。
- MyISAM 索引的叶子节点存储的是行数据地址,需要再寻址一次才能得到数据。
- InnoDB 非主键索引的叶子节点存储的是主键和其他带索引的列数据,因此查询时做到覆盖索引会非常高效。
两者都不是指单独的索引类型,而是指的索引数据的存储方式的不同
聚簇索引
指的是数据行跟相邻的键值紧凑的存储在一起;MyISAM的索引存储方式- 优点
- 1、可以把相关数据保存在一起
- 2、数据访问更快,因为索引和数据保存在同一个树中
- 3、使用覆盖索引扫描的查询可以直接使用页节点中的主键值
- 缺点
- 1、聚簇数据最大限度地提高了IO密集型应用的性能,如果数据全部在内存,那么聚簇索引就没有什么优势
- 2、插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式
- 3、更新聚簇索引列的代价很高,因为会强制将每个被更新的行移动到新的位置
- 4、基于聚簇索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能面临页分裂的问题
- 5、聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候
- 优点
非聚簇索引
数据文件跟索引文件分开存放;InnoDB的索引存储方式
-
回表
先通过辅助索引找到数据的主键,在根据主键扫描主键索引找出对应的数据内容,这个时候就会多扫描一次索引,这个就叫做回表
-
覆盖索引
查询的列从索引中就能获得,不必去回表或者从数据区域找出数据,通俗的讲就是查询的列就是索引;这就是覆盖索引
- 覆盖索引是一种非常强大的工具,能大大提高查询性能,只需要读取索引而不需要读取数据,有以下优点:
- 1、索引项通常比记录要小,所以MySQL访问更少的数据。
- 2、索引都按值得大小存储,相对于随机访问记录,需要更少的I/O。
- 3、数据引擎能更好的缓存索引,比如MyISAM只缓存索引。
- 4、覆盖索引对InnoDB尤其有用,因为InnoDB使用聚集索引组织数据,如果二级索引包含查询所需的数据,就不再需要在聚集索引中查找了。
- 限制:
- 1、覆盖索引也并不适用于任意的索引类型,索引必须存储列的值。
- 2、Hash和full-text索引不存储值,因此MySQL只能使用BTree。
- 3、不同的存储引擎实现覆盖索引都是不同的,并不是所有的存储引擎都支持覆盖索引。
- 4、如果要使用覆盖索引,一定要注意SELECT列表值取出需要的列,不可以SELECT * ,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。
- 覆盖索引是一种非常强大的工具,能大大提高查询性能,只需要读取索引而不需要读取数据,有以下优点:
-
最左匹配
对于复合索引,B+ Tree是按照从左到右进行匹配的;select中查询的索引条件不是按照索引从左到右顺序,依旧能使用到索引是因为mysql的优化器对sql进行了优化
-
索引下推
英文描述:Index Condition Pushdown;简称ICP。Mysql5.6的版本上推出,用于优化查询。
- 现象
- 1,在不使用ICP的情况下,在使用非主键索引(又叫辅助索引或其他)进行查询时,存储引擎通过索引检索到数据,然后返回给MySQL服务器,服务器然后判断数据是否符合条件 。
- 2,在使用ICP的情况下,如果存在某些被索引的列的判断条件时,MySQL服务器将这一部分判断条件传递给存储引擎,然后由存储引擎通过判断索引是否符合MySQL服务器传递的条件,只有当索引符合条件时才会将数据检索出来返回给MySQL服务器 。
- 示例
- 1,先准备一张用户表(user),其中主要几个字段有:id、name、age、address。建立联合索引(name,age)。
- 2,有一个需求,要求匹配姓名第一个为陈的所有用户,sql语句如下:SELECT * from user where name like ‘陈%’
根据 “最佳左前缀” 的原则,这里使用了联合索引(name,age)进行了查询,性能要比全表扫描肯定要高。 - 3,问题来了,如果有其他的条件呢?假设又有一个需求,要求匹配姓名第一个字为陈,年龄为20岁的用户,此时的sql语句如下:SELECT * from user where name like ‘陈%’ and age=20;这条sql语句应该如何执行呢?
- 现象
首先会忽略age条件,直接通过name进行查询,在(name,age)这课索引树上查找到了两个结果id分别为2,1,根据两个结果的id分别去查询主键索引树,最后查出结果,这里就会回表两次
索引下推执行过程:
不会忽略age条件,而是在索引内部就判断了age是否等于20,对于不等于20的记录直接跳过,因此在(name,age)这棵索引树中只匹配到了一个记录,此时拿着这个id去主键索引树中回表查询全部数据,这个过程只需要回表一次。
文章图片
- 总结
- 1,可以使用MySQL的执行计划查看Extra字段值是否有Using index condition,有的话就表名使用了索引下推
- 2,索引下推在非主键索引上(即辅助索引)的优化,可以有效减少回表的次数,大大提升了查询的效率。
- 3,关闭索引下推可以使用如下命令: set optimizer_switch=‘index_condition_pushdown=off’。
全值匹配
全值匹配指的是和索引中的所有列进行匹配匹配最左前缀
只匹配前面的几列匹配列前缀
可以匹配某一列的值的开头部分;这个方式可以设置字符串索引列的前几个字符,字段名(长度)匹配范围值
可以查找某一个范围的数据精确匹配某一列并范围匹配另外一列
可以查询第一列的全部和第二列的部分只访问索引的查询
查询的时候只需要访问索引,不需要访问数据行,本质上就是覆盖索引
优化细节
- 当使用索引列进行查询的时候尽量不要使用表达式,把计算放到业务层而不是数据库层
- 尽量使用主键查询,而不是其他索引,因此主键查询不会触发回表查询
- 使用前缀索引
- 数据准备,链接:https://pan.baidu.com/s/18KDKZxnhmWH9Ebpdhy6DlA 提取码:7665;提取citydemo.sql
- 案例
- 1,执行sql文件:citydemo.sql
- 2,前缀索引实例说明
有时候需要索引很长的字符串,这会让索引变的大且慢,通常情况下可以使用某个列开始的部分字符串,这样大大的节约索引空间,从而提高索引效率,但这会降低索引的选择性,索引的选择性是指不重复的索引值和数据表记录总数的比值,范围从1/#T到1之间。索引的选择性越高则查询效率越高,因为选择性更高的索引可以让mysql在查找的时候过滤掉更多的行。一般情况下某个列前缀的选择性也是足够高的,足以满足查询的性能,但是对应BLOB,TEXT,VARCHAR类型的列,必须要使用前缀索引,因为mysql不允许索引这些列的完整长度,使用该方法的诀窍在于要选择足够长的前缀以保证较高的选择性,通过又不能太长。 - 3,案例演示
--创建数据表
source citydemo.sql;
--查找最常见的城市列表,发现每个值都出现89-141次,
select count(*) as cnt,city from citydemo group by city order by cnt desc limit 10;
--查找最频繁出现的城市前缀,先从8个前缀字母开始,发现比原来出现的次数更多,可以分别截取多个字符查看城市出现的次数
select count(*) as cnt,left(city,8) as pref from citydemo group by pref order by cnt desc limit 10;
select count(*) as cnt,left(city,14) as pref from citydemo group by pref order by cnt desc limit 10;
--此时前缀的选择性接近于完整列的选择性--还可以通过另外一种方式来计算完整列的选择性,可以看到当前缀长度到达14之后,再增加前缀长度,选择性提升的幅度已经很小了
select count(distinct left(city,8))/count(*) as sel8,
count(distinct left(city,9))/count(*) as sel9,
count(distinct left(city,10))/count(*) as sel10,
count(distinct left(city,11))/count(*) as sel11,
count(distinct left(city,12))/count(*) as sel12,
count(distinct left(city,13))/count(*) as sel13,
count(distinct left(city,14))/count(*) as sel14,
count(distinct left(city,15))/count(*) as sel15,
count(distinct left(city,16))/count(*) as sel16
from citydemo;
--计算完成之后可以创建前缀索引
alter table citydemo add key(city(14));
--注意:前缀索引是一种能使索引更小更快的有效方法,但是也包含缺点:mysql无法使用前缀索引做order by 和 group by。
- 使用索引扫描来排序
- 数据准备,链接:https://pan.baidu.com/s/18KDKZxnhmWH9Ebpdhy6DlA 提取码:7665;提取rental.sql
- 案例
- 1,执行sql文件:rental.sql
- 2,使用索引扫描来做排序
mysql有两种方式可以生成有序的结果:通过排序操作或者按索引顺序扫描,如果explain出来的type列的值为index,则说明mysql使用了索引扫描来做排序
扫描索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录。但如果索引不能覆盖查询所需的全部列,那么就不得不每扫描一条索引记录就得回表查询一次对应的行,这基本都是随机IO,因此按索引顺序读取数据的速度通常要比顺序地全表扫描慢
mysql可以使用同一个索引即满足排序,又用于查找行,如果可能的话,设计索引时应该尽可能地同时满足这两种任务。
只有当索引的列顺序和order by子句的顺序完全一致,并且所有列的排序方式都一样时,mysql才能够使用索引来对结果进行排序,如果查询需要关联多张表,则只有当orderby子句引用的字段全部为第一张表时,才能使用索引做排序。order by子句和查找型查询的限制是一样的,需要满足索引的最左前缀的要求,否则,mysql都需要执行顺序操作,而无法利用索引排序 - 3,案例演示
-- 执行数据导入
source rental.sql;
--sakila数据库中rental表在rental_date,inventory_id,customer_id上有rental_date的索引
--使用rental_date索引为下面的查询做排序
explain select rental_id,staff_id from rental where rental_date='2005-05-25' order by inventory_id,customer_id\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: rental
partitions: NULL
type: ref
possible_keys: rental_date
key: rental_date
key_len: 5
ref: const
rows: 1
filtered: 100.00
Extra: Using index condition
1 row in set, 1 warning (0.00 sec)
--order by子句不满足索引的最左前缀的要求,也可以用于查询排序,这是因为所以你的第一列被指定为一个常数--该查询为索引的第一列提供了常量条件,而使用第二列进行排序,将两个列组合在一起,就形成了索引的最左前缀
explain select rental_id,staff_id from rental where rental_date='2005-05-25' order by inventory_id desc\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: rental
partitions: NULL
type: ref
possible_keys: rental_date
key: rental_date
key_len: 5
ref: const
rows: 1
filtered: 100.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)--下面的查询不会利用索引
explain select rental_id,staff_id from rental where rental_date>'2005-05-25' order by rental_date,inventory_id\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: rental
partitions: NULL
type: ALL
possible_keys: rental_date
key: NULL
key_len: NULL
ref: NULL
rows: 16005
filtered: 50.00
Extra: Using where;
Using filesort--该查询使用了两中不同的排序方向,但是索引列都是正序排序的
explain select rental_id,staff_id from rental where rental_date>'2005-05-25' order by inventory_id desc,customer_id asc\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: rental
partitions: NULL
type: ALL
possible_keys: rental_date
key: NULL
key_len: NULL
ref: NULL
rows: 16005
filtered: 50.00
Extra: Using where;
Using filesort
1 row in set, 1 warning (0.00 sec)--该查询中引用了一个不再索引中的列
explain select rental_id,staff_id from rental where rental_date>'2005-05-25' order by inventory_id,staff_id\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: rental
partitions: NULL
type: ALL
possible_keys: rental_date
key: NULL
key_len: NULL
ref: NULL
rows: 16005
filtered: 50.00
Extra: Using where;
Using filesort
1 row in set, 1 warning (0.00 sec)
- union all,in,or都能够使用索引,但是推荐使用in
- 范围列可以用到索引
- 范围条件是:<、<=、>、>=、between
- 范围列可以用到索引,但是范围列后面的列无法用到索引,索引最多用于一个范围列
- 强制类型转换会全表扫描
explain select * from user where phone=13800001234;
#不会触发索引
explain select * from user where phone='13800001234';
#触发索引
- 更新十分频繁,数据区分度不高的字段上不宜建立索引
- 更新会变更B+树,更新频繁的字段建议索引会大大降低数据库性能
- 类似于性别这类区分不大的属性,建立索引是没有意义的,不能有效的过滤数据
- 一般区分度在80%以上的时候就可以建立索引,区分度可以使用 count(distinct(列名))/count(*) 来计算
- 创建索引的列,不允许为null,可能会得到不符合预期的结果
- 当需要进行表连接的时候,最好不要超过三张表,因为需要join的字段,数据类型必须一致
- 能使用limit的时候尽量使用limit
- 单表索引建议控制在5个以内
- 单索引字段数不允许超过5个(组合索引)
- 创建索引的时候应该避免以下错误概念
- 索引越多越好
- 过早优化,在不了解系统的情况下进行优化
MySQL查询SQL执行时间 show profiles用来查询SQL执行时间,它是mysql 5.0.37之后添加的功能;select version(); 可查看MySQL版本。
1,查看show profiles功能是否开启,值为ON是开启,OFF是关闭
mysql > show variables like 'profiling';
2,开启show profiles功能
mysql > set profiling=1;
3,查看每条执行过的SQL语句的执行时间
mysql > show profiles;
4,查看一条SQL语句的详细执行时间
mysql > show profile for query ;
设置慢查询监控
1.查看慢查询相关参数
mysql > show variables like 'slow_query%';
mysql > show variables like 'long_query_time';
2.设置方法
方法一:全局变量设置
将 slow_query_log 全局变量设置为“ON”状态
mysql> set global slow_query_log='ON';
设置慢查询日志存放的位置
mysql> set global slow_query_log_file='/local/mysql-5.7.28/data/slow.log';
查询超过2秒就记录
mysql> set global long_query_time=2;
方法二:配置文件设置修改配置文件my.cnf,在[mysqld]下的下方加入
[mysqld]
slow_query_log=ON
slow_query_log_file=/local/mysql-5.7.28/data/slow.log
long_query_time=2
3.重启MySQL服务
service mysqld restart
4.查看设置后的参数
mysql > show variables like 'slow_query%';
mysql > show variables like 'long_query_time';
5.测试
执行一条慢查询SQL语句
mysql> select sleep(3);
查看是否生成慢查询日志
cat /local/mysql-5.7.28/data/slow.log
查看索引的使用情况
mysql> show status like 'Handler_read%';
文章图片
关于参数值的说明
- Handler_read_first: 此选项表明SQL是在做一个全索引扫描,注意是全部,而不是部分,所以说如果存在WHERE语句,这个选项是不会变的。如果这个选项的数值很大,既是好事 也是坏事。说它好是因为毕竟查询是在索引里完成的,而不是数据文件里,说它坏是因为大数据量时,简便是索引文件,做一次完整的扫描也是很费时的。
- Handler_read_key: 如果索引正在工作,这个值代表一个行被索引值读的次数,如果值越低,表示索引得到的性能改善不高,因为索引不经常使用(这个值越高越好,表明系统高效的使用了索引)。
- Handler_read_last: 读取索引最后一个条目的次数
- Handler_read_next: 按照键顺序读下一行的请求数。如果你用范围约束或如果执行索引扫描来查询索引列,该值增加。
- Handler_read_prev: 按照键顺序读前一行的请求数。该读方法主要用于优化ORDER BY … DESC。
- Handler_read_rnd: 根据固定位置读一行的请求数。如果你正执行大量查询并需要对结果进行排序该值较高。你可能使用了大量需要MySQL扫描整个表的查询或你的连接没有正确使用键。这个值较高,意味着运行效率低,应该建立索引来补救。
- Handler_read_rnd_next: 在数据文件中读下一行的请求数。如果你正进行大量的表扫描,该值较高。通常说明你的表索引不正确或写入的查询没有利用索引。
- 1,主键自动创建唯一索引
- 2,频繁作为查询条件的字段应该创建索引
- 3,查询中与其他表关联的字段,外键关系建立索引
- 4,频繁更新的字段不适合创建索引(因为每次更新不单单是更新了记录还会更新索引)
- 5,Where条件里用不到的字段不创建索引
- 6,单键/组合索引选择(在高并发下倾向创建组合索引)
- 7,查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
- 8,查询中统计或者分组字段
- 1,表记录太少
- 2,经常增删改的表及字段
- 3,数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。
假如一个表有10万行记录,有一个字段A只有T和F两种值,且每个值的分布概率大约50%,那么对这种表A字段建立索引一般不会提高数据库的查询速度。
索引的选择性是指索引列种不同值得数目与表种记录数的比。如果一个表中有2000条记录,表索引列有1980个不同的值,那么这个索引的选择性就是1980/2000=0.99。一个索引的选择性越接近于1,这个索引的效率就越高。
- 事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。
- 事务最经典也经常被拿出来说例子就是转账了。
假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
原子性(Atomicity)
原子性是事务是最小的执行单位,不允许分割。原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。举例来说,假设用户A和用户B两者的钱加起来一共是1000,那么不管A和B之间如何转账、转几次账,事务结束后两个用户的钱相加起来应该还得是1000,这就是事务的一致性。隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如同时操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
更新丢失(Lost update)
如果多个线程操作,基于同一个查询结构对表中的记录进行修改,那么后修改的记录将会覆盖前面修改的记录,前面的修改就丢失掉了,这就叫做更新丢失。这是因为系统没有执行任何的锁操作,因此并发事务并没有被隔离开来。- 第一类丢失更新:事务A撤销时,把已经提交的事务B的更新数据覆盖了。
时间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额1000元 | |
T4 | 查询账户余额1000元 | |
T5 | 汇入100元修改金额为1100元 | |
T6 | 提交事务 | |
T7 | 取出100元将余额修改为900元 | |
T8 | 撤销事务 | |
T9 | 余额恢复为1000元(丢失更新) |
- 第二类丢失更新: 事务A覆盖事务B已经提交的数据,造成事务B所做的操作丢失。
时间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额1000元 | |
T4 | 查询账户余额1000元 | |
T5 | 取出100元将余额修改为900元 | |
T6 | 提交事务 | |
T7 | 汇入100元将余额修改为1100元 | |
T8 | 提交事务 | |
T9 | 查询账户余额1100元(丢失更新) |
脏读(Dirty Read)
A事务读取B事务尚未提交的数据并在此基础上操作,而B事务执行回滚,那么A读取到的数据就是脏数据。不可重复读(Non-repeatable Read)
一个事务对同一行数据重复读取两次,但是却得到了不同的结果。事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时得到与前一次不同的值。虚读(幻读Phantom-Read)
指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。
如丙存款100元未提交,这时银行做报表统计account表中所有用户的总额为500元,然后丙提交了,这时银行再统计发现帐户为600元了,造成虚读同样会使银行不知所措,到底以哪个为准。可将例子简化为:读整个表,即表的行数,例如第一次读某个表有3条记录,第二次读该表又有4条记录。通常情况下幻读也正是我们所需要的
为了达到事务的四大特性;解决事务并发引起的问题
READ-UNCOMMITTED(读取未提交)
在READ-UNCOMMITTED隔离级别,所有事物都可以看到未提交事务的执行结果。在这种级别上,可能会产生很多问题,除非用户真的知道自己在做什么,并有很好的理由选择这样做。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好很多,而别的级别还有其他更多的有点。读取未提交数据,也被称之为“脏读(Dirty Read)”READ-COMMITTED(读取已提交)
大多数数据库系统的默认隔离级别是READ-COMMITTED(但这不是MySQL默认的!)。它满足了隔离的早先简单定义:一个事务在开始时,只能看见已经提交事务所做的改变,一个事务从开始到提交前,所做的任何数据改变都是不可见的。除非已经提交。这种隔离级别也支持所谓的“不可重复读(Nonrepeatable Read)”。这意味着用户运行同一语句两次,看到的结果是不同的。REPEATABLE-READ(可重复读)
这种隔离级别解决了READ-UNCOMMITTED隔离级别导致的问题。它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手问题:幻读(Phantom Read)。简单来说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影(Phantom)”行。InnonDB和Falcon存储引擎通过多版本并发控制机制解决了幻读问题。同样该隔离级别也是MySQL的默认隔离级别SERIALIZABLE(可串行化)
该隔离级别是最高级别的隔离级别,他通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,SERIALIZABLE是在每个读的数据行上加锁。在这个级别,可能导致大量的超时现象和锁竞争现象。很少看到有用户选择这种隔离级别,但如果用户的应用为了数据的稳定性,需要强制减少并发的话,也可以选择这种隔离级别。
事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重读) 并不会有任何性能损失。
InnoDB 存储引擎在 分布式事务 的情况下一般会用到 SERIALIZABLE(可串行化) 隔离级别。
Serializable隔离级别,虽然可避免所有问题,但性能、效率是最低的,原因是它采取的是锁表的方式,即单线程的方式,即有一个事务来操作这个表了,另外一个事务只能等在外面进不来。
文章图片
锁 为什么要有锁?
- 锁是计算机协调多个进程或线程并发访问某一资源的机制。
- 1,为了避免多事务并发处理导致数据不一致,所以多事务之间要隔离。
- 2,事务对某个数据对象操作前,先向系统发出请求,对其加锁。
- 3,加锁后,事务则对此数据对象具有一定的控制,在释放锁之前,其他事务不能更新此数据对象。
-
1、悲观锁
悲观锁,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度。因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制。也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统的数据访问层中实现了加锁机制,也无法保证外部系统不会修改数据。
- 悲观锁小结
悲观锁并不是适用于任何场景,它也有它存在的一些不足,因为悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。如果加锁的时间过长,其他用户长时间无法访问,影响了程序的并发访问性,同时这样对数据库性能开销影响也很大,特别是对长事务而言,这样的开销往往无法承受。所以与悲观锁相对的,我们有了乐观锁。
- 悲观锁小结
-
2、乐观锁
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以只会在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回用户错误的信息,让用户决定如何去做。实现乐观锁一般来说有以下2种方式:
- 使用版本号
使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。 - 使用时间戳
乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。
- 使用版本号
表级锁
开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。行级锁
开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。页面锁(gap锁,间隙锁)
开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
表共享读锁(Table Read Lock)
对MyISAM表的读锁,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;
文章图片
表独占写锁(Table Write Lock)
对 MyISAM表的写锁,则会阻塞其他用户对同一表的读和写操作;当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。
文章图片
MyISAM表的读操作与写操作之间,以及写操作之间是串行的!
共享锁(s):又称读锁
允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。排他锁(X):又称写锁
允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。
select …for update
语句,加共享锁可以使用 select … lock in share mode
语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。InnoDB间隙锁
间隙锁(Next-Key锁)
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的 索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁 (Next-Key锁)
1,做MySQL集群
例如:mysql cluster、mysql proxy、mysql replication、drdb等
有人会问mysql集群,跟分表有什么关系;虽然它不是实际意义上的分表,但是它起到了分表的作用,做集群的意义是:为数据库减轻负担,说白了就是减少sql排队队列中的数量,举个例子:有10个sql请求,如果放在一个数据库服务器的排队队列中,它要等很长时间,如果把这10个sql请求,分配到5个数据库服务器的排队队列中,一个服务器的队列中只有2个,这样等待整体的等待时间就会明显的缩短。- 优点:扩展性好,没有多个分表后的复杂操作。
- 缺点:单个表的数据量还是没有变,一次操作所化的时间还是那么多,硬件开销大。
总结:对于高并发的情况,分担请求量,降低总体的请求时间。
2,水平分表
水平分表是指:预先估计会出现大数据量并且访问频繁的表,将其分为若干个表
例如:QQ的登陆表。假设QQ的用户有100亿,如果只有一张表,每个用户登陆的时候数据库都要从这100亿中查找,会很慢很慢。如果将这一张表分成100份,每张表1亿条,就小了很多。比如qq0,qq1,…qq99表。用户登陆的时候,可以将用户的id%100,那么会得到0-99的数,查询表的时候,将表名qq跟取模的数连接起来,就构建了表名。比如用户id为1000086,除以100取模的86,那么就到qq86表查询,查询的时间将会大大缩短。这就是水平分割。- 优点:避免一张表出现几百万条数据,缩短了一条sql的执行时间。
- 缺点:当一种规则确定时,打破这条规则会狠麻烦。扩展性狠差。
3,垂直分表
垂直分割指的是:表的记录并不多,但是字段却很长,表占用空间很大,检索表的时候需要执行大量的IO,严重降低了性能。这时需要把大的字段拆分到另一个表,并且该表与原表是一对一的关系。
例如:学生答题表aa:有如下字段:
id,name,分数,题目,回答
如果我们只想查询id为8的学生的分数:select 分数 from aa where id=8; 虽然知识只是查询分数,但是题目和回答这两个大字段也是要被扫描的,狠消耗性能。但是我们只关系分数,并不想查询题目和回答。这就可以使用垂直分割。我们可以把题目单独放到一张表中,通过id与aa表建立一对一的关系,同样将回答单独放到一张表中。这样我们查询aa中的分数的时候就不会扫描题目和回答。
其他方式:
利用merge存储引擎来实现分表(与水平分表类似):需要了解merge存储引擎的机制
MySQL5.7.28_02_一张图片带你入门MySQL
MySQL5.7.28_03_一张图片带你进阶MySQL
MySQL5.7.28_03-1_一篇简单文章让你理解B- Tree和B+ Tree理解
MySQL5.7.28_03-2_MySQL中MyISAM与InnoDB引擎中的锁的简单理解
MySQL5.7.28_03-3_理解MySQL主从复制(也叫做主从同步)
MySQL5.7.28_03-4_MySQL 分布式ID方案总结
MySQL5.7.28_04_MySQL相关规范(实际工作中需要注意的)
推荐阅读
- 数据库|MySQL第04篇(JDBC)
- java|SpringCloud+MySQL+Vue实现人脸识别智能考勤管理系统
- Dapper,向数据库中增加数据
- Android 数据库 大量插入 事务开启
- Android SQLit数据库学习
- 安卓中的数据库操作
- MySQL|MySQL进阶之存储引擎【InnoDB、MySAM、Memory】
- 数据连接一切,开启融合数据云新时代——星环科技春季新品发布周盛大开启
- MySQL8.0基础学习|【MySQL】 表的必备知识●创建和管理