MySQL数据库查看索引占用的空间大小,查看是否存在锁和事务,以及事务的隔离级别测试

MySql BTree和Hash索引的比较,为什么InnoDB不使用Hash索引

MySql BTree和Hash索引的比较,为什么InnoDB不使用Hash索引

-1 为啥要用B+Tree而不用btree?

btree所有的节点都存储数据,而b+tree只有叶子节点才存储数据
而内存每次加载数据的大小是有限的,而有数据的时候,加载的量就会很小,如果都是索引,就会加载更多的索引值

索引覆盖:MySQL官网,类似的说法出现在explain查询计划优化章节,即explain的输出结果Extra字段为Using index时,能够触发索引覆盖。 其实简单些:只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。

  1. 主键索引当然不需要索引覆盖,因为本身能查询到所有的数据
  2. 非主键索引分为几个情况:

	1. 比如name是索引,id是主键,
	则select id,name  from XXX where name ='a'使用到了索引覆盖
	2. 联合索引情况,id主键,name version 是联合索引 ,
	则select id,name,version  from XXX where name ='a' and version='B' 使用到了索引覆盖

回表是指:在非主键索引的情况下,先查询到了主键,再通过主键查询数据

索引下推ICP:官网介绍

0. B+Tree能存多少数据

mysql> show global status like 'Innodb_page_size';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| Innodb_page_size | 16384 |
+------------------+-------+
1 row in set
  1. innodb的所有数据文件.idb大小都是16K即16384的整数倍
  2. 我们的InnoDB页的大小默认是16k?为啥是16K?因为16K的话,高度为3的树就可以存储千万级别的数据,如下有说明
  3. 假设一行数据的大小是1k,那么一个页可以存放16行这样的数据。
  4. 我们假设主键ID为bigint类型,长度为8字节,而指针大小在InnoDB源码中设置为6字节,这样一共14字节
  5. 我们一个页中能存放多少这样的单元,其实就代表有多少指针,即16384/14=1170。
  6. 那么可以算出一棵高度为2的B+树,即存在一个根节点和若干个叶子节点,那么这棵B+树的存放总记录数为:根节点指针数单个叶子节点记录行数。能存放117016=18720条这样的数据记录。
  7. 根据同样的原理我们可以算出一个高度为3的B+树可以存放:1170117016=21902400条这样的记录。

所以在InnoDB中B+树高度一般为1-3层,它就能满足千万级的数据存储。
在查找数据时一次页的查找代表一次IO,所以通过主键索引查询通常只需要1-3次IO操作即可查找到数据。

1. 统计数据库索引占用的空间大小

如果想知道MySQL数据库中每个表占用的空间、表记录的行数的话,可以打开MySQL的 information_schema 数据库。在该库中有一个 TABLES 表,这个表主要字段分别是:

TABLE_SCHEMA : 数据库名
TABLE_NAME:表名
ENGINE:所使用的存储引擎
TABLES_ROWS:记录数
DATA_LENGTH:数据大小
INDEX_LENGTH:索引大小

其他字段请参考MySQL的手册,我们只需要了解这几个就足够了。

  1. 首先查看某一实例下的所有占用磁盘空间
    (表数据+索引数据,得到的结果为B,这里做了数据处理转成M):
select concat(round((sum(DATA_LENGTH)+sum(INDEX_LENGTH))/1024/1024,2),'M')
 from information_schema.tables where table_schema='数据库名称';
  1. 上面是查询所有的表计的累计量,下面是是查询单个表计的的SQL(按照实例名查询):
select table_name,
DATA_LENGTH/1024/1024 as tablesData,
INDEX_LENGTH/1024/1024 as indexData 
from information_schema.tables
where table_schema='数据库名称'
ORDER BY  tablesData desc;

2.查看是否有表锁

1、查询正在使用哪个表
show OPEN TABLES where In_use > 0;

2、查询进程
show processlist
查询到相对应的进程===然后 kill id

补充:
查看正在锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; 

查看等待锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

 
# 查询是否有表锁
SELECT * from information_schema.INNODB_TRX
kill trx_mysql_thread_id

从 information_schema.innodb_trx 表中查看当前未提交的事务

select trx_state, trx_started, trx_mysql_thread_id, trx_query from information_schema.innodb_trx;
字段意义:

trx_state: 事务状态,一般为RUNNING
trx_started: 事务执行的起始时间,若时间较长,则要分析该事务是否合理
trx_mysql_thread_id: MySQL的线程ID,用于kill
trx_query: 事务中的sql
一般只要kill掉这些线程,DDL操作就不会Waiting for table metadata lock2. 调整锁超时阈值
lock_wait_timeout 表示获取metadata lock的超时(单位秒),允许的值范围为1315360001年)。 默认值为31536000 (1)。官方解释:https://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_lock_wait_timeout 。

修改等待时间:

set session lock_wait_timeout = 1800;
set global lock_wait_timeout = 1800;

3. MySQL8.0 查看默认事务的隔离级别,并测试

如下:MySQL默认是:可重复读

mysql> select @@global.transaction_isolation,@@transaction_isolation;
+--------------------------------+-------------------------+
| @@global.transaction_isolation | @@transaction_isolation |
+--------------------------------+-------------------------+
| REPEATABLE-READ                | REPEATABLE-READ         |
+--------------------------------+-------------------------+
1 row in set (0.00 sec)

3.1 更改隔离级别为:未提交读测试

两个命令行客户端分别为A,B;不断改变A的隔离级别,在B端修改数据。
但是不改变B的隔离级别,只改变A的,B还是默认的隔离级别:可重复读
A:

mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
## 查看
mysql>  select @@global.transaction_isolation,@@transaction_isolation;
+--------------------------------+-------------------------+
| @@global.transaction_isolation | @@transaction_isolation |
+--------------------------------+-------------------------+
| REPEATABLE-READ                | READ-UNCOMMITTED        |
+--------------------------------+-------------------------+
1 row in set (0.00 sec)

  1. 准一张表,测试事务
CREATE TABLE `test` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `num` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
## 插入3条数据
INSERT INTO `trans`.`test` (`id`, `num`) VALUES ('1', '1');
INSERT INTO `trans`.`test` (`id`, `num`) VALUES ('2', '2');
INSERT INTO `trans`.`test` (`id`, `num`) VALUES ('3', '3');

用两个窗口分别打开MySQL

mysql -uroot -p123456
  1. 窗口1: 启动事务A,数据库3条记录是初始状态
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)
  1. 窗口2. 启动事务B ,修改数据,但是不提交事务B
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> update test set num =33 where id =3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
## 在事务B内已经修改了
mysql> select * from test ;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |  33 |
+----+-----+
3 rows in set (0.00 sec)
  1. 窗口1 事务A 查看数据是否已经被修改
    发现已经被修改,这就是所谓的“脏读”
mysql> select * from test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |  33 |
+----+-----+
3 rows in set (0.00 sec)
  1. 窗口2 回滚事务B
mysql> rollback ;
Query OK, 0 rows affected (0.04 sec)
  1. 窗口1 事务A查看数据是否回滚
    发现已经回滚
mysql> select * from test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)
  1. 经过上面的实验可以得出结论,事务B更新了一条记录,但是没有提交,此时事务A可以查询出未提交记录。造成脏读现象。未提交读是最低的隔离级别

3.2 测试隔离级别为:提交读

把窗口A的隔离级别换为:提交读,窗口B的不换还是默认的

mysql> set session transaction isolation level read committed ;
Query OK, 0 rows affected (0.00 sec)

mysql>  select @@global.transaction_isolation,@@transaction_isolation;
+--------------------------------+-------------------------+
| @@global.transaction_isolation | @@transaction_isolation |
+--------------------------------+-------------------------+
| REPEATABLE-READ                | READ-COMMITTED          |
+--------------------------------+-------------------------+
1 row in set (0.00 sec)

  1. 窗口A开启事务,查询
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test ;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)
  1. 窗口2开启事务,修改数据,并查看
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> update test set num =22 where id =2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |  22 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)
  1. 窗口A再次查看数据有没有被更新
    发现没有,隔离级别生效
mysql> select * from test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)
  1. 窗口B 提交事务
mysql> commit ;
Query OK, 0 rows affected (0.08 sec)
  1. 窗口A 再次查询数据
    发现数据被修改了,符合预期
mysql> select * from test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |  22 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec

看是正常的代码,但是这个时候有什么问题?
有:在事务A中,两次读取的数据不一致,即不可重复读

  1. 经过上面的实验可以得出结论,已提交读隔离级别解决了脏读的问题,但是出现了不可重复读的问题,即事务A在两次查询的数据不一致,因为在两次查询之间事务B更新了一条数据。已提交读只允许读取已提交的记录,但不要求可重复读。

3.3 测试可重复读

  1. 事务A 设置可重读,并开启事务查看
mysql> set session transaction isolation level  repeatable  read ;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@global.transaction_isolation,@@transaction_isolation;
+--------------------------------+-------------------------+
| @@global.transaction_isolation | @@transaction_isolation |
+--------------------------------+-------------------------+
| REPEATABLE-READ                | REPEATABLE-READ         |
+--------------------------------+-------------------------+
1 row in set (0.00 sec)

## 查看数据
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test ;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |  22 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)
  1. 事务B查看事务,默认的即可
mysql> select @@global.transaction_isolation,@@transaction_isolation;
+--------------------------------+-------------------------+
| @@global.transaction_isolation | @@transaction_isolation |
+--------------------------------+-------------------------+
| REPEATABLE-READ                | REPEATABLE-READ         |
+--------------------------------+-------------------------+
1 row in set (0.00 sec)

## 事务B开启事务并修改id=1的数据
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test ;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |  22 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

mysql> update test set num =11 where id =1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from test ;
+----+-----+
| id | num |
+----+-----+
|  1 |  11 |
|  2 |  22 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)
  1. 窗口A 查看发现没有被修改,符合预期
mysql> select * from test ;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |  22 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)
  1. 窗口B提交事务并查看,发现数据已经被更新了
mysql> commit ;
Query OK, 0 rows affected (0.08 sec)

mysql> select * from test ;
+----+-----+
| id | num |
+----+-----+
|  1 |  11 |
|  2 |  22 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

  1. 窗口A查看数据
mysql> select * from test ;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |  22 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

发现数据依然没有被修改,符合可重复读的预期,即在一个事务内读取一个值的数据始终应该保持一致
但是!!!
有什么问题没有!!!——————》》数据不是最新的数据

  1. 如果我 再次开启事务并提交事务在窗口B插入一条记录呢?
    下面在B窗口中,但是不开启事务
mysql> start transaction;

mysql> insert into test (num) value(4);
Query OK, 1 row affected (0.07 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test;
+----+-----+
| id | num |
+----+-----+
|  1 |  11 |
|  2 |  22 |
|  3 |   3 |
|  4 |   4 |
+----+-----+
4 rows in set (0.00 sec)
  1. 窗口A 再次查看,发现还是没有获取到最新数据
mysql> select * from test ;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |  22 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec

  1. 把事务A提交了,获取数据,才发现是最新的
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test ;
+----+-----+
| id | num |
+----+-----+
|  1 |  11 |
|  2 |  22 |
|  3 |   3 |
|  4 |   4 |
+----+-----+
4 rows in set (0.00 sec

由以上的实验可以得出结论,可重复读隔离级别只允许读取已提交记录,而且在一个事务A两次读取一个记录期间,其他事务B如果更新该记录,事务A是看不到的。但该事务不要求与其他事务可串行化。例如,当一个事务可以找到由一个已提交事务更新的记录,但是可能产生幻读问题(注意是可能,因为数据库对隔离级别的实现有所差别)。像以上的实验,就没有出现数据幻读的问题。

3.3.1 模拟幻读

上述的实验没有出现幻读的现象,下面我们来模拟一下什么是幻读?

  1. 保证两个窗口AB的隔离级别:都是可重复读
select @@global.transaction_isolation,@@transaction_isolation;
+--------------------------------+-------------------------+
| @@global.transaction_isolation | @@transaction_isolation |
+--------------------------------+-------------------------+
| REPEATABLE-READ                | REPEATABLE-READ         |
+--------------------------------+-------------------------+
## 假设现在已经有了6条记录分别是
mysql> select * from test;
+----+-----+
| id | num |
+----+-----+
|  1 |  11 |
|  2 |  22 |
|  3 |   3 |
|  4 |   4 |
|  5 |  55 |
|  6 |  55 |
+----+-----+
6 rows in set (0.00 sec)
  1. 在A窗口开启事务,查询
mysql> start transaction ;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test;
+----+-----+
| id | num |
+----+-----+
|  1 |  11 |
|  2 |  22 |
|  3 |   3 |
|  4 |   4 |
|  5 |  55 |
|  6 |  55 |
+----+-----+
6 rows in set (0.00 sec)

  1. 在B窗口不开启事务直接插入一条记录,num=7
mysql> insert into test (num) values (7);
Query OK, 1 row affected (0.10 sec)
  1. 在A窗口查询,是否有num=7的记录
mysql> select * from test;
+----+-----+
| id | num |
+----+-----+
|  1 |  11 |
|  2 |  22 |
|  3 |   3 |
|  4 |   4 |
|  5 |  55 |
|  6 |  55 |
+----+-----+
6 rows in set (0.00 sec)

发现没有Num=7的记录,验证了可重复读的特性,符合预期,但是我们怎么模拟幻读呢?

  1. 在窗口A中添加一条记录,num也等于7,并查询
mysql> insert into test (num) values (7);
Query OK, 1 row affected (0.00 sec)

mysql> select * from test;
+----+-----+
| id | num |
+----+-----+
|  1 |  11 |
|  2 |  22 |
|  3 |   3 |
|  4 |   4 |
|  5 |  55 |
|  6 |  55 |
|  8 |   7 |
+----+-----+
7 rows in set (0.00 sec)

发现直接跳过了id=7的数据,也说明了肯定有其它的线程在操作,试想我们如果更新num=7的记录会更新几条呢?如果更新了1条就不是幻读,如果更新了2条就产生了幻读。下面验证一下

  1. 在窗口A中更新num=7的记录,并再次查询
mysql> update test set num =777 where num =7;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2  Changed: 2  Warnings: 0
mysql> select * from test;
+----+-----+
| id | num |
+----+-----+
|  1 |  11 |
|  2 |  22 |
|  3 |   3 |
|  4 |   4 |
|  5 |  55 |
|  6 |  55 |
|  7 | 777 |
|  8 | 777 |
+----+-----+
8 rows in set (0.00 sec)


哇!!! 结果匹配了两条,而且查询出来多了一条记录,这难道是幻读????YES ,这就是幻读

模拟成功,类似删除也有类似效果,我们不再模拟
大致思路是:在A窗口开启事务,此时查询到两条777的记录,在B窗口新添加一条记录num =777,再加上原来两条777的记录公有三条,此时在A窗口执行删除语句

mysql> delete from test where num =777;
Query OK, 3 rows affected (0.00 sec)

发现删除了3条,但是预期是2条,所以出现了幻读


3.4 可串行化就不测试了,就是顺序的问题,谁的事务先开启谁进行,其它的都要等待

3.5 MVCC

3.5.0 MVCC

(Multi-Version Concurrency Control多版本并发控制):
为了提高并发度,InnoDb提供了 「非锁定读」,即不需要等待访问行上的锁释放,读取行的一个快照即可。 既然是多版本读,那么肯定读不到隔壁事务的新插入数据了,所以解决了幻读。

3.5.1 MVCC与隔离级别

Read Uncommitted每次都读取记录的最新版本,会出现脏读,未实现MVCC
Serializable对所有读操作都加锁,读写发生冲突,不会使用MVCC
SELECT

(RR REPEATABLE-READ 可重复读级别)InnoDb检查每行数据,确保它们符合两个标准:

  1. 只查找创建时间早于当前事务id的记录,这确保当前事务读取的行都是事务之前已经存在的,
  2. 或者是由当前事务创建或修改的行
  3. 行的DELETE BIT为1时,查找删除时间晚于当前事务id的记录,确定了当前事务开始之前,行没有被删除
    (RC级别)每次重新计算read view,read view的范围为InnoDb中最大的事务id,为避免脏读读取的是DB_ROLL_PT指向的记录

就这么简单吗? 其实幻读有很多种出现形式,简单的SELECT不加条件的查询在RR下肯定是读不到隔壁事务提交的数据的。但是仍然可能在执行INSERT/UPDATE时遇到幻读现象。因为SELECT 不加锁的快照读行为是无法限制其他事务对新增重合范围的数据的插入的。

所以还要引入第二个机制。

3.5.2 Next-Key Lock

其实更多的幻读现象是通过写操作来发现的,如SELECT了3条数据,UPDATE的时候可能返回了4个成功结果,或者INSERT某条不在的数据时忽然报错说唯一索引冲突等。
首先来了解一下InnoDb的锁机制,InnoDB有三种行锁:

Record Lock:单个行记录上的锁
Gap Lock:间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况
Next-Key Lock:前两个锁的加和,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题

如果是带排他锁操作(除了INSERT/UPDATE/DELETE这种,还包括SELECT FOR UPDATE/LOCK IN SHARE MODE等),它们默认都在操作的记录上加了Next-Key Lock。只有使用了这里的操作后才会在相应的记录周围和记录本身加锁,即Record Lock + Gap Lock,所以会导致有冲突操作的事务阻塞进而超时失败。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章