先完成再完美,先功能再性能。 大胆假设,小心求证。
MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 公司。
MySQL 是一种关联数据库管理系统,将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
MySQL 是开源的,所以你不需要支付额外的费用。
MySQL 支持大型的数据库。可以处理拥有上千万条记录的大型数据库。
MySQL 使用标准的 SQL 数据语言形式。
MySQL 可以允许于多个系统上,并且支持多种语言。这些编程语言包括 C、C++、Python、Java、Perl、PHP、Eiffel、Ruby 和 Tcl 等。
MySQL 对 PHP 有很好的支持,PHP 是目前最流行的 Web 开发语言。
MySQL 支持大型数据库,支持 5000 万条记录的数据仓库,32 位系统表文件最大可支持 4GB,64 位系统支持最大的表文件为 8TB。
MySQL 是可以定制的,采用了 GPL 协议,你可以修改源码来开发自己的 Mysql 系统。
完整的 mysql 优化需要很深的功底,大公司甚至有专门的 DBA 写下述内容:
官网下载地址:http://dev.mysql.com/downloads/mysql/
选择对应的 MySQL 版本、操作系统 和 位数,点击 Download(本博文中以版本 5.5.48 为例)
下载地址: https://downloads.mysql.com/archives/get/file/MySQL-server-5.5.48-1.linux2.6.x86_64.rpm https://downloads.mysql.com/archives/get/file/MySQL-client-5.5.48-1.linux2.6.x86_64.rpm
第一步:以 root 身份登录,查看 mysql 是否安装。
rpm -qa | grep mysql
第二步:以 root 身份登录,如果 mysql 的版本不是想要的版本。需要把原来的 mysql 卸载。
使用 yum 命令卸载
yum remove mysql mysql-server mysql-libs mysql-common
使用 rpm 命令卸载
rpm -e RPM软件包名(该名字是第一步命令查出来的名字)
rm -rf /var/lib/mysql
rm -f /etc/my.cnf
注意
:使用 yum 命令卸载,因为 yum 命令可以自动删除与 mysql 相关的依赖;如果使用 rpm 命令卸载,则还需要手动去删除和mysql相关的文件。
第三步1:安装 mysql。需要使用 yum 命令安装。在安装 mysql 之前需要安装 mysql 的下载源。需要从 oracle 的官方网站下载。
1)下载 mysql 的源包:
我们是 centos6.8 对应的 rpm 包为:mysql-community-release-el6-5.noarch.rpm
命令:wget http://repo.mysql.com/mysql-community-release-el6-5.noarch.rpm
2)安装 mysql 下载源:
yum localinstall mysql-community-release-el6-5.noarch.rpm
3)在线安装社区版的 mysql:
yum install mysql-community-server
或者在线安装收费版本的 mysql:
yum install mysql-server
两种方式均可,建议安装社区版。
第三步2:我们也可以使用 rpm 命令进行安装。 安装 mysql 服务端(注意提示)
安装 mysql 客户端
查看 MySQL 安装时创建的 mysql 用户和 mysql 组
或者可以执行 mysqladmin --version
命令。类似 java -version
,如果打出消息,即为安装成功。
mysql 服务的启+停
设置开机自启动 mysql 服务
查看 设置开机自启动 mysql 服务
第四步:启动 mysql。
service mysql start
注意:
第五步:需要给 root 用户设置密码。
/usr/bin/mysqladmin -u root password 'new-password' #为 root 账号设置密码
第六步:登录 mysql。
[root@localhost ~]# mysql -uroot -p
第七步:需要先登录到 mysql,进行远程连接授权。
GRANT ALL PRIVILEGES ON *.* TO 'myuser'@'%' IDENTIFIED BY 'mypassword' WITH GRANT OPTION;
注意
:'myuser'、'mypassword' 需要替换成实际的用户名和密码。
Mysql 5.5 版本:cp /usr/share/mysql/my-huge.cnf /etc/my.cnf
Mysql 5.6 版本:cp /usr/share/mysql/my-default.cnf /etc/my.cnf
1、查看字符集
mysql> show variables like 'character%'; 或者
mysql> show variables like '%char%';
默认的 数据库 和 服务器都用了 latin1,所以会乱码。
2、修改 下图中的红色字体部分表示要修改的地方
my.cnf 中要修改的代码如下:
[client]
#password = your_password
port = 3306
socket = /var/lib/mysql/mysql.sock
default-character-set=utf8
# The MySQL server
[mysqld]
port = 3306
character_set_server=utf8
character_set_client=utf8
collation-server=utf8_general_ci
socket = /var/lib/mysql/mysql.sock
skip-external-locking
key_buffer_size = 384M
max_allowed_packet = 1M
table_open_cache = 512
sort_buffer_size = 2M
read_buffer_size = 2M
read_rnd_buffer_size = 8M
myisam_sort_buffer_size = 64M
thread_cache_size = 8
query_cache_size = 32M
# Try number of CPU's*2 for thread_concurrency
thread_concurrency = 8
lower_case_table_names=1
max_connections=1000
[mysql]
no-auto-rehash
default-character-set=utf8
3、重启 mysql
service mysql stop
service mysql start
4、重新连接后重新 create databse 并使用新建库,然后再重新建表试试。
5、还是乱码的话就设值 init_connect='SET NAMES utf8' ##设定连接 mysql 是 UTF8 编码
。
在 linux 下查看安装目录,命令:ps -ef | grep mysql
/var/lib/mysql
和其它数据库相比,MySQL 有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构
上,插件式的存储引擎架构
将查询处理和其它的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
首先,mysql 的查询流程大致是:mysql 客户端通过协议与 mysql 服务器建连接,发送查询语句,先检查查询缓存,如果命中,直接返回结果,否则进行语句解析。
有一系列预处理,比如检查语句是否写正确了,然后是查询优化(比如是否使用索引扫描,如果是一个不可能的条件,则提前终止),生成查询计划,然后查询引擎启动,开始执行查询,从底层存储引擎调用 API 获取数据,最后返回给客户端。怎么存数据、怎么取数据,都与存储引擎有关。
然后,mysql 默认使用的 BTREE 索引,并且一个大方向是,无论怎么折腾 sql,至少在目前来说,mysql 最多只用到表中的一个索引。
详解见链接:https://www.cnblogs.com/chenmingjun/p/11012827.html
mysql 索引结构
检索原理
【初始化介绍】 一颗 b+ 树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块 1 包含数据项 17 和 35,包含指针 P1、P2、P3,P1 表示小于 17 的磁盘块,P2 表示在 17 和 35 之间的磁盘块,P3 表示大于 35 的磁盘块。 真实的数据存在于叶子节点即 3、5、9、10、13、15、28、29、36、60、75、79、90、99。 非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如 17、35 并不真实存在于数据表中。
【查找过程】 如果要查找数据项 29,那么首先会把磁盘块 1 由磁盘加载到内存,此时发生一次 IO,在内存中用二分查找确定 29 在 17 和 35 之间,锁定磁盘块 1 的 P2 指针,内存时间因为非常短(相比磁盘的 IO)可以忽略不计,通过磁盘块 1 的 P2 指针的磁盘地址把磁盘块 3 由磁盘加载到内存,发生第二次 IO,29 在 26 和 30 之间,锁定磁盘块 3 的 P2 指针,通过指针加载磁盘块 8 到内存,发生第三次 IO,同时内存中做二分查找找到 29,结束查询,总计三次 IO。
真实的情况是,3 层的 b+ 树可以表示上百万的数据,如果上百万的数据查找只需要三次 IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次 IO,那么总共需要百万次的 IO,显然成本非常非常高。
MySql Query Optimizer (MySQL 查询优化器) 1、Mysql 中有专门负责优化 SELECT 语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的 Query 提供他认为最优的执行计划(MySQL 认为最优的数据检索方式,但不见得是 DBA 认为是最优的,这部分最耗费时间)
2、当客户端向 MySQL 请求一条 Query,命令解析器模块完成请求分类,区别出是 SELECT 并转发给 MySQL Query Optimizer 时,MySQL Query Optimizer 首先会对整条 Query 进行优化,处理掉一些常量表达式的预算,直接换算成常量值。并对 Query 中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析 Query 中的 Hint 信息(如果有),看显示 Hint 信息是否可以完全确定该 Query 的执行计划。如果没有 Hint 或 Hint 信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据 Query 进行写相应的计算分析,然后再得出最后的执行计划。
1、单表 建表 SQL
CREATE TABLE IF NOT EXISTS `article` (
`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`author_id` INT(10) UNSIGNED NOT NULL,
`category_id` INT(10) UNSIGNED NOT NULL,
`views` INT(10) UNSIGNED NOT NULL,
`comments` INT(10) UNSIGNED NOT NULL,
`title` VARBINARY(255) NOT NULL,
`content` TEXT NOT NULL
);
INSERT INTO `article`(`author_id`, `category_id`, `views`, `comments`, `title`, `content`) VALUES
(1, 1, 1, 1, '1', '1'),
(2, 2, 2, 2, '2', '2'),
(1, 1, 3, 3, '3', '3');
SELECT * FROM article;
案例
# 查询 category_id 为 1 且 comments 大于 1 的情况下,views 最多的 article_id。
# 1.0 第 1 次EXPLAIN
EXPLAIN SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
+----+-------------+---------+------+---------------+------+---------+------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+------+---------------+------+---------+------+------+-----------------------------+
| 1 | SIMPLE | article | ALL | NULL | NULL | NULL | NULL | 3 | Using where; Using filesort |
+----+-------------+---------+------+---------------+------+---------+------+------+-----------------------------+
#结论:很显然,type 是 ALL,即最坏的情况。Extra 里还出现了 Using filesort,也是最坏的情况。优化是必须的。
# 开始优化:
# 1.1 新建索引+删除索引
#ALTER TABLE `article` ADD INDEX idx_article_ccv (`category_id`, `comments`, `views`);
CREATE INDEX idx_article_ccv ON article (category_id, comments, views);
DROP INDEX idx_article_ccv ON article;
# 1.2 第 2 次EXPLAIN
EXPLAIN SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
+----+-------------+---------+-------+-----------------+-----------------+---------+------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+-------+-----------------+-----------------+---------+------+------+-----------------------------+
| 1 | SIMPLE | article | range | idx_article_ccv | idx_article_ccv | 8 | NULL | 1 | Using where; Using filesort |
+----+-------------+---------+-------+-----------------+-----------------+---------+------+------+-----------------------------+
EXPLAIN SELECT id, author_id FROM article WHERE category_id = 1 AND comments = 3 ORDER BY views DESC LIMIT 1;
+----+-------------+---------+------+-----------------+-----------------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+------+-----------------+-----------------+---------+-------------+------+-------------+
| 1 | SIMPLE | article | ref | idx_article_ccv | idx_article_ccv | 8 | const,const | 1 | Using where |
+----+-------------+---------+------+-----------------+-----------------+---------+-------------+------+-------------+
# 结论:
# type 变成了 range,这是可以忍受的。但是 extra 里使用 Using filesort 仍是无法接受的。
# 但是我们已经建立了索引,为啥没用呢?
# 这是因为按照 BTree 索引的工作原理:(分组之前必排序)
# 先排序 category_id,
# 如果遇到相同的 category_id 则再排序 comments,如果遇到相同的 comments 则再排序 views。
# 当 comments 字段在联合索引里处于中间位置时,
# 因 comments > 1 条件是一个范围值(所谓 range),
# MySQL 无法利用索引再对后面的 views 部分进行检索,即 range 类型查询字段后面的索引无效。
# 1.3 删除第 1 次建立的索引
DROP INDEX idx_article_ccv ON article;
# 1.4 第 2 次新建索引
#ALTER TABLE `article` ADD INDEX idx_article_cv (`category_id`, `views`);
CREATE INDEX idx_article_cv ON article (category_id, views);
# 1.5 第 3 次 EXPLAIN
EXPLAIN SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
+----+-------------+---------+------+----------------+----------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+------+----------------+----------------+---------+-------+------+-------------+
| 1 | SIMPLE | article | ref | idx_article_cv | idx_article_cv | 4 | const | 2 | Using where |
+----+-------------+---------+------+----------------+----------------+---------+-------+------+-------------+
# 结论:可以看到,type 变为了 ref,Extra 中的 Using filesort 也消失了,结果非常理想。
# 删除第 2 次建立的索引
DROP INDEX idx_article_cv ON article;
2、两表
CREATE TABLE IF NOT EXISTS `class` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `book` (
`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`bookid`)
);
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO class (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO book (card) VALUES (FLOOR(1 + (RAND() * 20)));
select * from class;
select * from book;
案例
# 下面开始第 2 次 explain 分析
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | class | ALL | NULL | NULL | NULL | NULL | 20 | |
| 1 | SIMPLE | book | ALL | NULL | NULL | NULL | NULL | 20 | |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
# 结论:type 有 All
# 添加索引优化
ALTER TABLE `book` ADD INDEX Y (`card`);
# 第 2 次 explain
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
+----+-------------+-------+------+---------------+------+---------+-------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------------------+------+-------------+
| 1 | SIMPLE | class | ALL | NULL | NULL | NULL | NULL | 20 | |
| 1 | SIMPLE | book | ref | Y | Y | 4 | db2019.class.card | 1 | Using index |
+----+-------------+-------+------+---------------+------+---------+-------------------+------+-------------+
# 可以看到第二行的 type 变为了 ref,rows 也变成了优化比较明显。
# 这是由左连接特性决定的。LEFT JOIN 条件用于确定如何从右表搜索行,左表一定都有,
# 所以右表是我们的关键点,一定需要建立索引。
# 删除旧索引 + 新建新索引 + 第 3 次 explain
DROP INDEX Y ON book;
ALTER TABLE `class` ADD INDEX X (`card`);
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
+----+-------------+-------+-------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | class | index | NULL | X | 4 | NULL | 20 | Using index |
| 1 | SIMPLE | book | ALL | NULL | NULL | NULL | NULL | 20 | |
+----+-------------+-------+-------+---------------+------+---------+------+------+-------------+
# 然后来看一个右连接查询(同理):
# 优化较明显。这是因为 RIGHT JOIN 条件用于确定如何从左表搜索行,右表一定都有,
# 所以左表是我们的关键点,一定需要建立索引。
EXPLAIN SELECT * FROM class RIGHT JOIN book ON class.card = book.card;
DROP INDEX X ON class;
ALTER TABLE `book` ADD INDEX Y (`card`);
# 右连接,基本无变化
EXPLAIN SELECT * FROM class RIGHT JOIN book ON class.card = book.card;
3、三表
在上面二表的基础上再加一张表
CREATE TABLE IF NOT EXISTS `phone` (
`phoneid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`phoneid`)
) ENGINE = INNODB;
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
INSERT INTO phone (card) VALUES (FLOOR(1 + (RAND() * 20)));
案例
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card LEFT JOIN phone ON book.card = phone.card;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | class | ALL | NULL | NULL | NULL | NULL | 20 | |
| 1 | SIMPLE | book | ALL | NULL | NULL | NULL | NULL | 20 | |
| 1 | SIMPLE | phone | ALL | NULL | NULL | NULL | NULL | 20 | |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
以左连接为例:(将右边对应的字段建立索引)
ALTER TABLE `phone` ADD INDEX z (`card`);
ALTER TABLE `book` ADD INDEX Y (`card`);
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card LEFT JOIN phone ON book.card = phone.card;
+----+-------------+-------+------+---------------+------+---------+-------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------------------+------+-------------+
| 1 | SIMPLE | class | ALL | NULL | NULL | NULL | NULL | 20 | |
| 1 | SIMPLE | book | ref | Y | Y | 4 | db2019.class.card | 1 | Using index |
| 1 | SIMPLE | phone | ref | z | z | 4 | db2019.book.card | 1 | Using index |
+----+-------------+-------+------+---------------+------+---------+-------------------+------+-------------+
# 后 2 行的 type 都是 ref 且总 rows 优化很好,效果不错。因此索引最好设置在需要经常查询的字段中。
===============================================================================================
【结论】
join 语句的优化:
1、尽可能减少 join 语句中的 Nested Loop(嵌套循环) 的循环总次数;“永远用小结果集驱动大的结果集”(小表驱动大表);
2、优先优化 Nested Loop(嵌套循环) 的内层循环;
3、保证 join 语句中的被驱动表上 join 条件字段已经被索引;
4、当无法保证被驱动表的 join 条件字段被索引且内存资源充足的前提下,不要太吝惜 join buffer 的设置(即将该值调大些);
详解见链接:https://www.cnblogs.com/chenmingjun/p/11012827.html
面试题讲解: 我们创建了复合索引 idx_test03_c1234,根据以下 SQL 分析下索引使用情况?
【建表语句】
create table test03 (
id int primary key not null auto_increment,
c1 char(10),
c2 char(10),
c3 char(10),
c4 char(10),
c5 char(10)
);
insert into test03 (c1,c2,c3,c4,c5) values ('a1','a2','a3','a4','a5');
insert into test03 (c1,c2,c3,c4,c5) values ('b1','b2','b3','b4','b5');
insert into test03 (c1,c2,c3,c4,c5) values ('c1','c2','c3','c4','c5');
insert into test03 (c1,c2,c3,c4,c5) values ('d1','d2','d3','d4','d5');
insert into test03 (c1,c2,c3,c4,c5) values ('e1','e2','e3','e4','e5');
select * from test03;
【建索引】
create index idx_test03_c1234 on test03(c1, c2, c3, c4);
show index from test03;
问题:我们创建了复合索引 idx_test03_c1234,根据以下 SQL 分析下索引使用情况?
explain select * from test03 where c1='a1';
explain select * from test03 where c1='a1' and c2='a2';
explain select * from test03 where c1='a1' and c2='a2' and c3='a3';
explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4='a4';
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 31 | const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
+----+-------------+--------+------+------------------+------------------+---------+-------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 93 | const,const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------------+------+-------------+
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 124 | const,const,const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
===================================================================================================================================
1) explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4='a4'; # 索引都使用到了,因为 MySQL 底层进行了优化
2) explain select * from test03 where c1='a1' and c2='a2' and c4='a4' and c3='a3'; # 索引都使用到了,因为 MySQL 底层进行了优化
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 124 | const,const,const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 124 | const,const,const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------------------+------+-------------+
结论:索引的使用顺序与索引的创建顺序无关,但是最好按照创建索引的顺序使用索引。这样可以避免一次的 sql 自身的查询优化。
3) explain select * from test03 where c1='a1' and c2='a2' and c3>'a3' and c4='a4'; # 使用到了 c1、c2、c3索引,c3 范围之后全失效
4) explain select * from test03 where c1='a1' and c2='a2' and c4>'a4' and c3='a3'; # 索引都使用到了,因为 MySQL 底层进行了优化,c4 范围之后全失效,但是 c4 之后没有范围了
+----+-------------+--------+-------+------------------+------------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+-------+------------------+------------------+---------+------+------+-------------+
| 1 | SIMPLE | test03 | range | idx_test03_c1234 | idx_test03_c1234 | 93 | NULL | 1 | Using where |
+----+-------------+--------+-------+------------------+------------------+---------+------+------+-------------+
+----+-------------+--------+-------+------------------+------------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+-------+------------------+------------------+---------+------+------+-------------+
| 1 | SIMPLE | test03 | range | idx_test03_c1234 | idx_test03_c1234 | 124 | NULL | 1 | Using where |
+----+-------------+--------+-------+------------------+------------------+---------+------+------+-------------+
5) explain select * from test03 where c1='a1' and c2='a2' and c4='a4' order by c3; # 索引 c3 作用在排序而不是查找(只是用到了一半,但是不算)
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
6) explain select * from test03 where c1='a1' and c2='a2' order by c3; # 使用到了 c1、c2 索引
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
7) explain select * from test03 where c1='a1' and c2='a2' order by c4; # 使用到了 c1、c2 索引,出现了 filesort,性能下降
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-----------------------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | Using where; Using filesort |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-----------------------------+
8)
8.1 explain select * from test03 where c1='a1' and c5='a5' order by c2, c3;
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 31 | const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
只用到了 c1 一个字段索引,但是 c2、c3 用于排序,无 filesort。
8.2 explain select * from test03 where c1='a1' and c5='a5' order by c3, c2;
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-----------------------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 31 | const | 1 | Using where; Using filesort |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-----------------------------+
只用到了 c1 一个字段索引,且出现了 filesort,我们建的索引是 1234,它没有按照顺序来,3 2 颠倒了。
9) explain select * from test03 where c1='a1' and c2='a2' order by c2, c3;
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
用到了 c1、c2 两个字段索引,但是 c2、c3 用于排序,无 filesort。
10)
explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c2, c3;
结果同 (9),用到了 c1、c2 两个字段索引,但是 c2、c3 用于排序,无 filesort。
explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c3, c2;
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------------+------+-------------+
本例有常量 c2 的情况,和 8.2 进行对比,无 filesort。
小结:一般来说,order by 的字段顺序没有跟创建索引的字段顺序一致时,一般会出现 filesort,但是排序字段有常量的情况时,那么 order by 的字段顺序没有跟创建索引的字段顺序一致时 则不会出现 filesort,案例如上。
explain select * from test03 where c1='a1' and c5='a5' order by c3, c2;
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-----------------------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 31 | const | 1 | Using where; Using filesort |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-----------------------------+
只用到了 c1 一个字段索引,且出现了 filesort。
11)explain select * from test03 where c1='a1' and c4='a4' group by c2, c3;
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 31 | const | 1 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
只用到了 c1 一个字段索引,但是 c2、c3 用于排序,无 filesort。
12)explain select * from test03 where c1='a1' and c4='a4' group by c3, c2;
+----+-------------+--------+------+------------------+------------------+---------+-------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+----------------------------------------------+
| 1 | SIMPLE | test03 | ref | idx_test03_c1234 | idx_test03_c1234 | 31 | const | 1 | Using where; Using temporary; Using filesort |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+----------------------------------------------+
只用到了 c1 一个字段索引,且出现了 temporary 和 filesort。因为分组之前必排序(内部排序)。
小结: 1、定值、范围还是排序,一般 order by 是给个范围。 2、group by 基本上都需要进行内部排序,如果 group by 字段顺序错了之后,内部会有临时表产生。
面试问题:你对 Mysql 的优化有哪些? 答: 1、代码至少跑一天,观察慢 sql 的情况。 2、开启慢查询日志,设置阈值,比如超过 5 秒钟的慢 sql,并将它捕获出来。 3、explain + 慢 sql 分析。 4、show profile,查看 sql z在服务器里面的执行细节和生命周期情况。 5、运维经理或者 DBA 进行 sql 数据库服务器的参数调优。
类似嵌套循环(Nested Loop)
1、ORDER BY 子句,尽量使用 index 方式排序,避免使用 filesort 方式排序。
建表 SQL
CREATE TABLE tblA (
#id int primary key not null auto_increment,
age INT,
birth TIMESTAMP NOT NULL
);
INSERT INTO tblA (age,birth) VALUES (22, NOW());
INSERT INTO tblA (age,birth) VALUES (23, NOW());
INSERT INTO tblA (age,birth) VALUES (24, NOW());
CREATE INDEX idx_A_ageBirth ON tblA(age, birth);
SELECT * FROM tblA;
案例1
mysql> explain select * from tblA where age > 20 order by age;
+----+-------------+-------+-------+----------------+----------------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+----------------+----------------+---------+------+------+--------------------------+
| 1 | SIMPLE | tblA | index | idx_A_ageBirth | idx_A_ageBirth | 9 | NULL | 3 | Using where; Using index |
+----+-------------+-------+-------+----------------+----------------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
mysql> explain select * from tblA where age > 20 order by age, birth;
+----+-------------+-------+-------+----------------+----------------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+----------------+----------------+---------+------+------+--------------------------+
| 1 | SIMPLE | tblA | index | idx_A_ageBirth | idx_A_ageBirth | 9 | NULL | 3 | Using where; Using index |
+----+-------------+-------+-------+----------------+----------------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
mysql> explain select * from tblA where age > 20 order by birth;
+----+-------------+-------+-------+----------------+----------------+---------+------+------+------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+----------------+----------------+---------+------+------+------------------------------------------+
| 1 | SIMPLE | tblA | index | idx_A_ageBirth | idx_A_ageBirth | 9 | NULL | 3 | Using where; Using index; Using filesort |
+----+-------------+-------+-------+----------------+----------------+---------+------+------+------------------------------------------+
1 row in set (0.00 sec)
mysql> explain select * from tblA where age > 20 order by birth, age;
+----+-------------+-------+-------+----------------+----------------+---------+------+------+------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+----------------+----------------+---------+------+------+------------------------------------------+
| 1 | SIMPLE | tblA | index | idx_A_ageBirth | idx_A_ageBirth | 9 | NULL | 3 | Using where; Using index; Using filesort |
+----+-------------+-------+-------+----------------+----------------+---------+------+------+------------------------------------------+
1 row in set (0.00 sec)
mysql>
截图如下:
案例2
mysql> explain select * from tblA order by birth;
+----+-------------+-------+-------+---------------+----------------+---------+------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+----------------+---------+------+------+-----------------------------+
| 1 | SIMPLE | tblA | index | NULL | idx_A_ageBirth | 9 | NULL | 3 | Using index; Using filesort |
+----+-------------+-------+-------+---------------+----------------+---------+------+------+-----------------------------+
1 row in set (0.00 sec)
mysql> explain select * from tblA where birth > '2019-06-12 00:00:00' order by birth;
+----+-------------+-------+-------+---------------+----------------+---------+------+------+------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+----------------+---------+------+------+------------------------------------------+
| 1 | SIMPLE | tblA | index | NULL | idx_A_ageBirth | 9 | NULL | 3 | Using where; Using index; Using filesort |
+----+-------------+-------+-------+---------------+----------------+---------+------+------+------------------------------------------+
1 row in set (0.00 sec)
mysql> explain select * from tblA where birth > '2019-06-12 00:00:00' order by age;
+----+-------------+-------+-------+---------------+----------------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+----------------+---------+------+------+--------------------------+
| 1 | SIMPLE | tblA | index | NULL | idx_A_ageBirth | 9 | NULL | 3 | Using where; Using index |
+----+-------------+-------+-------+---------------+----------------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
mysql> explain select * from tblA order by age asc, birth desc;
+----+-------------+-------+-------+---------------+----------------+---------+------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+----------------+---------+------+------+-----------------------------+
| 1 | SIMPLE | tblA | index | NULL | idx_A_ageBirth | 9 | NULL | 3 | Using index; Using filesort |
+----+-------------+-------+-------+---------------+----------------+---------+------+------+-----------------------------+
1 row in set (0.00 sec)
mysql>
截图如下:
结论: 1、MySQL 支持二种方式的排序:filesort 和 index,index 效率高。 2、它指 MySQL 扫描索引本身完成排序。filesort 方式效率较低。
ORDER BY 满足两种情况时,会使用 index方式排序: 1、ORDER BY 语句使用索引最左前列。 2、使用 where 子句与 order by 子句条件列组合满足索引最左前列。
2、尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀原则。
3、如果不在索引列上,filesort 有两种算法:mysql 就要启动双路排序和单路排序。
why
提高 order by 的速度:
1、order by 时 select * 是一个大忌只 query 需要的字段,这点非常重要。在这里的影响是:
1.1 当 query 的字段大小总和小于 max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法--单路排序,否则用老算法--多路排序。
1.2 两种算法的数据都有可能超出 sort_buffer 的容量,超出之后,会创建 tmp 文件进行合并排序,导致多次 I/O,但是用单路排序算法的风险会更大一些,所以要提高 sort_buffer_size。
2、尝试提高 sort_buffer_size
不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的。
3、尝试提高 max_length_for_sort_data
提高这个参数,会增加用改进算法的概率。但是如果设的太高,数据总容量超出 sort_buffer_size 的概率就增大,明显症状是高的磁盘 I/O 活动和低的处理器使用率。
小结
是什么
怎么玩
说明
查看是否开启和如何开启
使用 set global slow_query_log=1; 开启了慢查询日志只对当前数据生效,如果 MySQL 重启后则会失效。 如果要永久生效,就必须修改配置文件 my.cnf(其他系统变量亦是如此)
那么开启了慢查询日志后,什么样的 sql 才会记录到慢查询日志里面呢?
案例
日志分析工具 mysqldumpslow 在生产环境中,如果手工分析日志、查找、分析SQL,显然是个体力活,MySQL 提供了日志分析工具 mysqldumpslow。
参数详解及示例
详解见链接:https://www.cnblogs.com/chenmingjun/p/11012827.html
这招只允许在测试环境下使用,永远不允许在生产环境下用。
配置启动:
编码启用:
Mysql 分区是什么?(PARTITION) 如果一张表的数据量太大的话,那么 myd,myi 就会变的很大,查找数据就会变的很慢,这个时候我们可以利用 mysql 的分区功能,在物理上将这一张表对应的三个文件,分割成许多个小块,这样呢,我们查找一条数据时,就不用全部查找了,只要知道这条数据在哪一块,然后在那一块找就行了。如果表的数据太大,可能一个磁盘放不下,这个时候,我们可以把数据分配到不同的磁盘里面去。
Range 分区案例 建表 SQL
CREATE TABLE tbl_new (
id INT NOT NULL PRIMARY KEY,
title VARCHAR(20) NOT NULL DEFAULT ''
) ENGINE MYISAM CHARSET utf8
PARTITION BY RANGE(id) (
PARTITION t0 VALUES LESS THAN(10),
PARTITION t1 VALUES LESS THAN(20),
PARTITION t2 VALUES LESS THAN(MAXVALUE)
);
INSERT INTO tbl_new VALUES (1,'z3');
INSERT INTO tbl_new VALUES (2,'z4');
INSERT INTO tbl_new VALUES (3,'z5');
INSERT INTO tbl_new VALUES (4,'z6');
查看分区
插入数据后再次查看分区
原理: MySQL 中的 LIST 分区在很多方面类似于 RANGE 分区。和按照 RANGE 分区一样,每个分区必须明确定义。它们的主要区别在于,LIST 分区中每个分区的定义和选择是基于某列的值从属于一个值列表集中的一个值,而 RANGE 分区是从属于一个连续区间值的集合。
List 分区案例 建表 SQL
create table area (
id INT NOT NULL PRIMARY KEY,
region varchar(20)
)engine myisam charset utf8;
insert into area values (1,'bj');
insert into area values (2,'sh');
insert into area values (3,'gz');
insert into area values (4,'sz');
create table user (
uid int not null,
userName varchar(20),
area_id int
)engine myisam charset utf8
partition by list(area_id) (
partition bj values in (1),
partition sh values in (2),
partition gz values in (3),
partition sz values in (4)
);
insert into user (uid, userName, area_id) values (1,'z3',1);
insert into user (uid, userName, area_id) values (2,'z4',2);
insert into user (uid, userName, area_id) values (3,'z5',3);
查看分区
MySQL 中的分区在禁止空值 NULL 上没有进行处理,无论它是一个列值还是一个用户定义表达式的值,一般而言,在这种情况下 MySQL 把 NULL 当做零。如果你不希望出现类似情况,建议在设计表时声明该列 “NOT NULL”。
锁是计算机协调多个进程或线程并发访问某一资源的机制。 在数据库中,除传统的计算资源(如 CPU、RAM、I/O 等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
锁的分类
建表 SQL
【表级锁分析--建表 SQL】
create table mylock (
id int not null primary key auto_increment,
name varchar(20)
)engine myisam;
insert into mylock (name) values('a');
insert into mylock (name) values('b');
insert into mylock (name) values('c');
insert into mylock (name) values('d');
insert into mylock (name) values('e');
select * from mylock;
【手动增加表锁】
lock table tableName1 read/write, tableName2 read/write, 其它;
【查看表上加过的锁】
show open tables;
【释放表锁】
unlock tables;
我们为 mylock 表加 read 锁(读阻塞写例子)
我们为 mylock 表加 write 锁(MyISAM 存储引擎的写阻塞读例子)
案例结论
表锁分析
事务(Transaction)及其 ACID 属性:
事务是由一组 SQL 语句组成的逻辑处理单元,事务具有以下 4 个属性,通常简称为事务的 ACID 属性。
原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如 B 树索引或双向链表)也都必须是正确的。
隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的 “独立” 环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
并发事务处理带来的问题:
更新丢失(Lost Update):最后的更新覆盖了由其他事务所做的更新。如果在一个程序员在完成并提交事务之前,另一个程序员不能访问同一文件,则可避免此问题。
脏读(Dirty Reads):事务 A 读取到了事务 B 已修改但尚未提交的的数据,还在这个数据基础上做了操作。此时,如果 B 事务回滚,A 读取的数据无效,不符合一致性要求。(读到了错误的数据)
不可重复读(Non-Repeatable Reads):事务 A 读取到了事务 B 已经提交的修改数据,不符合隔离性。(有个数据读多次,居然读的不一样)
幻读(Phantom Reads):事务 A 读取到了事务 B 体提交的新增数据,不符合隔离性。(读的数据变多了,出现了幻觉)
多说一句:幻读和脏读有点类似:
脏读是事务 B 里面修改了数据,
幻读是事务 B 里面新增了数据。
事务隔离级别:
脏读”、“不可重复读” 和 “幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。
同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。
查看当期数据库的事务隔离级别命令:show variables like 'tx_isolation';
隔离级别图解
建表 SQL
create table test_innodb_lock (a int(11), b varchar(16)) engine=innodb;
insert into test_innodb_lock values (1,'b2');
insert into test_innodb_lock values (3,'3');
insert into test_innodb_lock values (4,'4000');
insert into test_innodb_lock values (5,'5000');
insert into test_innodb_lock values (6,'6000');
insert into test_innodb_lock values (7,'7000');
insert into test_innodb_lock values (8,'8000');
insert into test_innodb_lock values (9,'9000');
insert into test_innodb_lock values (1,'b1');
create index test_innodb_a_ind on test_innodb_lock (a);
create index test_innodb_lock_b_ind on test_innodb_lock (b);
select * from test_innodb_lock;
行锁定基本演示
索引失效行锁升级为表锁
间隙锁危害
面试题:如何锁应一行
案列结论
Innodb 存储引擎由于实现了行级锁定
,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,但是在整体并发处理能力方面要远远优于 MyISAM 的表级锁定的。当系统并发量较高的时候,Innodb 的整体性能和 MyISAM 相比就会有比较明显的优势了。
但是,Innodb 的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让 Innodb 的整体性能表现不仅不能比 MyISAM高,甚至可能会更差。
行锁分析
优化建议
原理图