目录
一,子查询与表连接
首先准备测试用的表结构及数据:(将代码复制到txt文件,修改后缀名为sql并保存,在navicat 中新建数据库,右击将要创建表的数据库,选择运行sql文件,右击【表 / 新建的数据库】刷新即可看到新建的表)
/*
Navicat Premium Data Transfer
Source Server : Mysql
Source Server Type : MySQL
Source Server Version : 50728
Source Host : localhost:3306
Source Schema : chuange
Target Server Type : MySQL
Target Server Version : 50728
File Encoding : 65001
Date: 04/06/2020 10:52:20
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for customers
-- ----------------------------
DROP TABLE IF EXISTS `customers`;
CREATE TABLE `customers` (
`cust_id` int(11) NOT NULL AUTO_INCREMENT,
`cust_name` char(50) NOT NULL,
`cust_address` char(50) DEFAULT NULL,
`cust_city` char(50) DEFAULT NULL,
`cust_state` char(5) DEFAULT NULL,
`cust_zip` char(10) DEFAULT NULL,
`cust_country` char(50) DEFAULT NULL,
`cust_contact` char(50) DEFAULT NULL,
`cust_email` char(255) DEFAULT NULL,
PRIMARY KEY (`cust_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=10006 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Records of customers
-- ----------------------------
BEGIN;
INSERT INTO `customers` VALUES (10001, 'Coyote Inc.', '200 Maple Lane', 'Detroit', 'MI', '44444', 'USA', 'Y Lee', 'ylee@coyote.com');
INSERT INTO `customers` VALUES (10002, 'Mouse House', '333 Fromage Lane', 'Columbus', 'OH', '43333', 'USA', 'Jerry Mouse', NULL);
INSERT INTO `customers` VALUES (10003, 'Wascals', '1 Sunny Place', 'Muncie', 'IN', '42222', 'USA', 'Jim Jones', 'rabbit@wascally.com');
INSERT INTO `customers` VALUES (10004, 'Yosemite Place', '829 Riverside Drive', 'Phoenix', 'AZ', '88888', 'USA', 'Y Sam', 'sam@yosemite.com');
INSERT INTO `customers` VALUES (10005, 'E Fudd', '4545 53rd Street', 'Chicago', 'IL', '54545', 'USA', 'E Fudd', NULL);
COMMIT;
-- ----------------------------
-- Table structure for orderitems
-- ----------------------------
DROP TABLE IF EXISTS `orderitems`;
CREATE TABLE `orderitems` (
`order_num` int(11) NOT NULL,
`order_item` int(11) NOT NULL,
`prod_id` char(10) NOT NULL,
`quantity` int(11) NOT NULL,
`item_price` decimal(8,2) NOT NULL,
PRIMARY KEY (`order_num`,`order_item`) USING BTREE,
KEY `fk_orderitems_products` (`prod_id`) USING BTREE,
CONSTRAINT `fk_orderitems_orders` FOREIGN KEY (`order_num`) REFERENCES `orders` (`order_num`),
CONSTRAINT `fk_orderitems_products` FOREIGN KEY (`prod_id`) REFERENCES `products` (`prod_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Records of orderitems
-- ----------------------------
BEGIN;
INSERT INTO `orderitems` VALUES (20005, 1, 'ANV01', 10, 5.99);
INSERT INTO `orderitems` VALUES (20005, 2, 'ANV02', 3, 9.99);
INSERT INTO `orderitems` VALUES (20005, 3, 'TNT2', 5, 10.00);
INSERT INTO `orderitems` VALUES (20005, 4, 'FB', 1, 10.00);
INSERT INTO `orderitems` VALUES (20006, 1, 'JP2000', 1, 55.00);
INSERT INTO `orderitems` VALUES (20007, 1, 'TNT2', 100, 10.00);
INSERT INTO `orderitems` VALUES (20008, 1, 'FC', 50, 2.50);
INSERT INTO `orderitems` VALUES (20009, 1, 'FB', 1, 10.00);
INSERT INTO `orderitems` VALUES (20009, 2, 'OL1', 1, 8.99);
INSERT INTO `orderitems` VALUES (20009, 3, 'SLING', 1, 4.49);
INSERT INTO `orderitems` VALUES (20009, 4, 'ANV03', 1, 14.99);
COMMIT;
-- ----------------------------
-- Table structure for orders
-- ----------------------------
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
`order_num` int(11) NOT NULL AUTO_INCREMENT,
`order_date` datetime DEFAULT NULL,
`cust_id` int(11) NOT NULL,
PRIMARY KEY (`order_num`) USING BTREE,
KEY `fk_orders_customers` (`cust_id`) USING BTREE,
CONSTRAINT `fk_orders_customers` FOREIGN KEY (`cust_id`) REFERENCES `customers` (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=20010 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Records of orders
-- ----------------------------
BEGIN;
INSERT INTO `orders` VALUES (20005, '2005-09-01 00:00:00', 10001);
INSERT INTO `orders` VALUES (20006, '2005-09-12 00:00:00', 10003);
INSERT INTO `orders` VALUES (20007, '2005-09-30 00:00:00', 10004);
INSERT INTO `orders` VALUES (20008, '2005-10-03 00:00:00', 10005);
INSERT INTO `orders` VALUES (20009, '2005-10-08 00:00:00', 10001);
COMMIT;
-- ----------------------------
-- Table structure for productnotes
-- ----------------------------
DROP TABLE IF EXISTS `productnotes`;
CREATE TABLE `productnotes` (
`note_id` int(11) NOT NULL AUTO_INCREMENT,
`prod_id` char(10) NOT NULL,
`note_date` datetime DEFAULT NULL,
`note_text` text,
PRIMARY KEY (`note_id`) USING BTREE,
FULLTEXT KEY `note_text` (`note_text`)
) ENGINE=MyISAM AUTO_INCREMENT=115 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Records of productnotes
-- ----------------------------
BEGIN;
INSERT INTO `productnotes` VALUES (101, 'TNT2', '2005-08-17 00:00:00', 'Customer complaint:\r\nSticks not individually wrapped, too easy to mistakenly detonate all at once.\r\nRecommend individual wrapping.');
INSERT INTO `productnotes` VALUES (102, 'OL1', '2005-08-18 00:00:00', 'Can shipped full, refills not available.\r\nNeed to order new can if refill needed.');
INSERT INTO `productnotes` VALUES (103, 'SAFE', '2005-08-18 00:00:00', 'Safe is combination locked, combination not provided with safe.\r\nThis is rarely a problem as safes are typically blown up or dropped by customers.');
INSERT INTO `productnotes` VALUES (104, 'FC', '2005-08-19 00:00:00', 'Quantity varies, sold by the sack load.\r\nAll guaranteed to be bright and orange, and suitable for use as rabbit bait.');
INSERT INTO `productnotes` VALUES (105, 'TNT2', '2005-08-20 00:00:00', 'Included fuses are short and have been known to detonate too quickly for some customers.\r\nLonger fuses are available (item FU1) and should be recommended.');
INSERT INTO `productnotes` VALUES (106, 'TNT2', '2005-08-22 00:00:00', 'Matches not included, recommend purchase of matches or detonator (item DTNTR).');
INSERT INTO `productnotes` VALUES (107, 'SAFE', '2005-08-23 00:00:00', 'Please note that no returns will be accepted if safe opened using explosives.');
INSERT INTO `productnotes` VALUES (108, 'ANV01', '2005-08-25 00:00:00', 'Multiple customer returns, anvils failing to drop fast enough or falling backwards on purchaser. Recommend that customer considers using heavier anvils.');
INSERT INTO `productnotes` VALUES (109, 'ANV03', '2005-09-01 00:00:00', 'Item is extremely heavy. Designed for dropping, not recommended for use with slings, ropes, pulleys, or tightropes.');
INSERT INTO `productnotes` VALUES (110, 'FC', '2005-09-01 00:00:00', 'Customer complaint: rabbit has been able to detect trap, food apparently less effective now.');
INSERT INTO `productnotes` VALUES (111, 'SLING', '2005-09-02 00:00:00', 'Shipped unassembled, requires common tools (including oversized hammer).');
INSERT INTO `productnotes` VALUES (112, 'SAFE', '2005-09-02 00:00:00', 'Customer complaint:\r\nCircular hole in safe floor can apparently be easily cut with handsaw.');
INSERT INTO `productnotes` VALUES (113, 'ANV01', '2005-09-05 00:00:00', 'Customer complaint:\r\nNot heavy enough to generate flying stars around head of victim. If being purchased for dropping, recommend ANV02 or ANV03 instead.');
INSERT INTO `productnotes` VALUES (114, 'SAFE', '2005-09-07 00:00:00', 'Call from individual trapped in safe plummeting to the ground, suggests an escape hatch be added.\r\nComment forwarded to vendor.');
COMMIT;
-- ----------------------------
-- Table structure for products
-- ----------------------------
DROP TABLE IF EXISTS `products`;
CREATE TABLE `products` (
`prod_id` char(10) NOT NULL,
`vend_id` int(11) NOT NULL,
`prod_name` char(255) NOT NULL,
`prod_price` decimal(8,2) NOT NULL,
`prod_desc` text,
PRIMARY KEY (`prod_id`) USING BTREE,
KEY `fk_products_vendors` (`vend_id`) USING BTREE,
CONSTRAINT `fk_products_vendors` FOREIGN KEY (`vend_id`) REFERENCES `vendors` (`vend_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Records of products
-- ----------------------------
BEGIN;
INSERT INTO `products` VALUES ('ANV01', 1001, '.5 ton anvil', 5.99, '.5 ton anvil, black, complete with handy hook');
INSERT INTO `products` VALUES ('ANV02', 1001, '1 ton anvil', 9.99, '1 ton anvil, black, complete with handy hook and carrying case');
INSERT INTO `products` VALUES ('ANV03', 1001, '2 ton anvil', 14.99, '2 ton anvil, black, complete with handy hook and carrying case');
INSERT INTO `products` VALUES ('DTNTR', 1003, 'Detonator', 13.00, 'Detonator (plunger powered), fuses not included');
INSERT INTO `products` VALUES ('FB', 1003, 'Bird seed', 10.00, 'Large bag (suitable for road runners)');
INSERT INTO `products` VALUES ('FC', 1003, 'Carrots', 2.50, 'Carrots (rabbit hunting season only)');
INSERT INTO `products` VALUES ('FU1', 1002, 'Fuses', 3.42, '1 dozen, extra long');
INSERT INTO `products` VALUES ('JP1000', 1005, 'JetPack 1000', 35.00, 'JetPack 1000, intended for single use');
INSERT INTO `products` VALUES ('JP2000', 1005, 'JetPack 2000', 55.00, 'JetPack 2000, multi-use');
INSERT INTO `products` VALUES ('OL1', 1002, 'Oil can', 8.99, 'Oil can, red');
INSERT INTO `products` VALUES ('SAFE', 1003, 'Safe', 50.00, 'Safe with combination lock');
INSERT INTO `products` VALUES ('SLING', 1003, 'Sling', 4.49, 'Sling, one size fits all');
INSERT INTO `products` VALUES ('TNT1', 1003, 'TNT (1 stick)', 2.50, 'TNT, red, single stick');
INSERT INTO `products` VALUES ('TNT2', 1003, 'TNT (5 sticks)', 10.00, 'TNT, red, pack of 10 sticks');
COMMIT;
-- ----------------------------
-- Table structure for vendors
-- ----------------------------
DROP TABLE IF EXISTS `vendors`;
CREATE TABLE `vendors` (
`vend_id` int(11) NOT NULL AUTO_INCREMENT,
`vend_name` char(50) NOT NULL,
`vend_address` char(50) DEFAULT NULL,
`vend_city` char(50) DEFAULT NULL,
`vend_state` char(5) DEFAULT NULL,
`vend_zip` char(10) DEFAULT NULL,
`vend_country` char(50) DEFAULT NULL,
PRIMARY KEY (`vend_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1007 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Records of vendors
-- ----------------------------
BEGIN;
INSERT INTO `vendors` VALUES (1001, 'Anvils R Us', '123 Main Street', 'Southfield', 'MI', '48075', 'USA');
INSERT INTO `vendors` VALUES (1002, 'LT Supplies', '500 Park Street', 'Anytown', 'OH', '44333', 'USA');
INSERT INTO `vendors` VALUES (1003, 'ACME', '555 High Street', 'Los Angeles', 'CA', '90046', 'USA');
INSERT INTO `vendors` VALUES (1004, 'Furball Inc.', '1000 5th Avenue', 'New York', 'NY', '11111', 'USA');
INSERT INTO `vendors` VALUES (1005, 'Jet Set', '42 Galaxy Road', 'London', NULL, 'N16 6PS', 'England');
INSERT INTO `vendors` VALUES (1006, 'Jouets Et Ours', '1 Rue Amusement', 'Paris', NULL, '45678', 'France');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
1,子查询(嵌套sql)
SELECT语句是SQL的查询。迄今为⽌我们所看到的所有SELECT语句都是简单查询,即从单个数据库表中检索数据的单条语句。
SQL还允许创建⼦查询(subquery),即嵌套在其他查询中的查询。
- 就是在一个sql当中,它的where条件来源于另外一个sql;
- 或者反过来理解,一个sql语句的结果,作为外层sql语句的条件。
2,利用子查询进行过滤
接下来将通过实例来理解子查询:
已知条件
- 订单存储在两个表中。对于包含订单号、客户ID、订单⽇期的每个订单,orders表存储⼀⾏。
- 各订单的物品存储在相关的orderitems表中。
- orders表不存储客户信息。它只存储客户的ID。实际的客户信息存储在customers表中。
现在,假如需要列出订购物品TNT2的所有客户,应该怎样检索?
直观流程如下:
1,在orderitems表中找到prod_id为TNT2的订单编号order_num
SELECT order_num, prod_id FROM orderitems WHERE prod_id = 'TNT2'
运行,结果如下:
2,在订单表中找到order_num对应的顾客idcust_id
SELECT cust_id, order_num FROM orders WHERE order_num in(20005, 20007)
3,在顾客表中找到顾客id对应的顾客信息
SELECT cust_name, cust_contact, cust_id FROM customers WHERE cust_id in(10001, 10004)
可以把其中的WHERE⼦句转换为⼦查询⽽不是硬编码这些SQL返回的数据
SELECT cust_id, cust_name
FROM customers
WHERE cust_id IN (
SELECT cust_id
FROM orders
WHERE order_num IN (
SELECT order_num
FROM orderitems
WHERE prod_id = 'TNT2' )
);
- 为了执⾏上述SELECT语句,MySQL实际上必须执⾏3条SELECT语句。
- 最⾥边的⼦查询返回订单号列表,此列表⽤于其外⾯的⼦查询的WHERE⼦句。
- 外⾯的⼦查询返回客户ID列表,此客户ID列表⽤于最外层查询的WHERE⼦句。
- 最外层查询确实返回所需的数据。
3,作为计算字段使用子查询
假如需要显示customers表中每个客户的订单总数。订单与相应的客户ID存储在orders表中。
1,从customers表中获取用户列表
select cust_id,cust_name from customers
2,先获取一个用户在orders表中的订单数
select count(*) as orders_num from orders where cust_id = 10001;
3,考虑如何获取每个客户的订单数,对每个客户进行count函数的统计计算(即作为计算字段使用子查询)
select cust_id,cust_name,
(select count(*) from orders where orders.cust_id = customers.cust_id) as orders_num
from customers;
注意:这里计算字段使用子查询时,采用完全限定表名
二,表关系
1,关系表
假如有⼀个包含产品⽬录的数据库表,其中每种类别的物品占⼀⾏。
对于每种物品要存储的信息包括产品描述和价格,以及⽣产该产品的供应商信息。
现在,假如有由同⼀供应商⽣产的多种物品,那么在何处存储供应商信息(如,供应商名、地址、联系⽅法等)呢?
可以看出,若供应商相同,供应商之后的字段将会全部一致
相同数据出现多次决不是⼀件好事,此因素是关系数据库设计的基础。
关系表的设计就是要保证把信息分解成多个表,⼀类数据⼀个表。
各表通过某些常⽤的值(即关系设计中的关系(relational))互相关联。
在这个例⼦中,可建⽴两个表,⼀个存储供应商信息,另⼀个存储产品信息。
vendors表的主键⼜叫作products的外键,它将vendors表与products表关联,利⽤供应商ID能 从vendors表中找出相应供应商的详细信息。 这样做的好处如下:
- 供应商信息不重复,从⽽不浪费时间和空间;
- 如果供应商信息变动,可以只更新vendors表中的单个记录,相关表中的数据不⽤改动;
- 由于数据⽆重复,显然数据是⼀致的,这使得处理数据更简单
关系数据可以有效地存储和⽅便地处理。因此,关系数据库的可伸缩性远⽐⾮关系数据库要好。
2,一对一关系与外键
2.1 外键
表关系:表与表之间的关系
外键:
- 在一个表中,定义一个字段,这个字段中存储的数据是另外一张表中的主键
- 就是在一个表中的字段,代表着这个数据属于谁
了解:
外键实现的方式,有两种:物理外键、逻辑外键
物理外键:
- 就是在创建表时,就指定这个表中的字段是一个外键,并且强关联某个表中的某个字段
- 需要在定义字段时,使用sql语句来实现
- 缺点:删除当前表时需要先删除其依赖的表;由于强关系的存在,并发时效率会受到影响;
逻辑外键:推荐
- 就是在表中创建一个普通的字段,没有强关联关系,需要通过程序逻辑来实现
一对一:
就是在一个表中的数据,对应着另外一张表中的一个数据,只能有一个
举例:
员工表:
- id,姓名、性别、年龄、籍贯、联系方式、学历、工龄、。。。。
由上面的一个表,拆分成两个表
上面的表关系就是一对一的表关系,通过详情表中的yid这个字段来标记员工表中的主键。
一个员工有着一个对应的详情信息,存储在详情表中,
在详情表中的数据,也只属于某一个员工。
3,一对多
在一个表中的一条数据对应着另外一个表中的多条数据
在一个表中的多条数据,对应着另外一张表中一个数据
4,多对多
举例:例如一本书,有多个标签,同时每一个标签下又对应多本书
三,表联结
1,where与join的用法
1.1 使用where进行表联结
如果数据存储在多个表中,怎样⽤单条SELECT语句检索出数据?
答案是使⽤联结。简单地说,联结是⼀种机制,⽤来在⼀条SELECT语句中关联表,因此称之为联结。
使⽤特殊的语法,可以联结多个表返回⼀组输出,联结在运⾏时关联表中正确的⾏。
例如:我们需要查询出所有的商品及对应的供应商信息怎么办?
select vend_name,prod_name,prod_price
from vendors,products
where vendors.vend_id = products.vend_id
order by vend_name,prod_name;
这两个表⽤WHERE⼦句正确联结,WHERE⼦句指示MySQL匹配vendors表中的vend_id和products表中的vend_id。
可以看到要匹配的两个列以 vendors.vend_id 和 products. vend_id指定。这⾥需要这种完全限定列名,因为如果只给出vend_id,则MySQL不知道指的是哪⼀个(它们有两个,每个表中⼀个)。
在引⽤的列可能出现⼆义性时,必须使⽤完全限定列名(⽤⼀个点分隔的表名和列名)
在联结两个表时,你实际上做的是将第⼀个表中的每⼀⾏与第⼆个表中的每⼀⾏配对。
WHERE⼦句作为过滤条件,它只包含那些匹配给定条件(这⾥是联结条件)的⾏。
你能想象上⾯的sql如果没有where条件时会怎样吗?
select vend_name,prod_name,prod_price from vendors,products
如果没有where条件,第⼀个表中的每个⾏将与第⼆个表中的每个⾏配对,⽽不管它们逻辑上是否可以配在⼀起
由没有联结条件的表关系返回的结果为笛卡⼉积。检索出的⾏的数⽬将是第⼀个表中的⾏数乘以第⼆个表中的⾏数。
不要忘了WHERE⼦句
应该保证所有联结都有WHERE⼦句,否则MySQL将返回⽐想要的数据多得多的数据。
同理,应该保证WHERE⼦句的正确性。不正确的过滤条件将导致MySQL返回不正确的数据
1.2 使用join进行表联结
其实,对于这种联结可以使⽤稍微不同的语法来明确指定联结的类型。
select vend_name,prod_name,prod_price
from vendors
inner join products
on vendors.vend_id = products.vend_id;
两个表之间的关系是FROM⼦句的组成部分,以INNER JOIN指定。
在使⽤这种语法时,联结条件⽤特定的ON⼦句⽽不是WHERE⼦句给出。
传递给ON的实际条件与传递给WHERE的相同。
SQL规范⾸选INNER JOIN语法。(这里直接使用JOIN结果相同)
1.3 联结多个表
SQL对一条SELECT语句中可以联结的表的数目没有限制。
创建联结的基本规则也相同。首先列出所有表,然后定义表之间的关系。
案例:查询出订单号为20005的订单中购买的商品及对应的产品供应商信息
select prod_name,vend_name,prod_price,quantity
from orderitems,products,vendors
where products.vend_id = vendors.vend_id
and orderitems.prod_id = products.prod_id
and order_num = 20005;
改写为join语法
select prod_name,vend_name,prod_price,quantity
from orderitems
inner join products on orderitems.prod_id = products.prod_id
inner join vendors on products.vend_id = vendors.vend_id
where order_num = 20005;
MySQL在运⾏时关联指定的每个表以处理联结。 这种处理可能是⾮常耗费资源的,因此应该仔细,不要联结不必要的表。联结的表越多,性能下降越厉害。
1.4 使用表别名AS
别名除了⽤于列名和计算字段外,SQL还允许给表名起别名。
这样做有两个主要理由:
- 缩短SQL语句;
- 允许在单条SELECT语句中多次使⽤相同的表
应该注意,表别名只在查询执⾏中使⽤。与列别名不⼀样,表别名不返回到客户机
2,联结查询中的自联结
自联结:当前这个表与自己这个表 做联结(join)
举例
假如你发现某物品(其ID为DTNTR)存在问题,因此想知道⽣产该物品的供应商⽣产的其他物品是否也存在这些问题。
此查询要求⾸先找到⽣产ID为DTNTR的物品的供应商,然后找出这个供应商⽣产的其他物品。
-- 使⽤⼦查询(嵌套查询)
select prod_id,prod_name
from products
where vend_id = (select vend_id from products where prod_id = 'DTNTR');
-- 使用 自联结方式查询
select p1.prod_id,p2.prod_name
from products as p1
join products as p2
on p1.vend_id = p2.vend_id
where p2.prod_id = 'DTNTR';
-- 改成where语句
select p1.prod_id,p2.prod_name
from products as p1, products as p2
where p1.vend_id = p2.vend_id and p2.prod_id = 'DTNTR';
-- 此查询中需要的两个表实际上是相同的表,因此products表在FROM⼦句中出现了两次。虽然这是完全合法的,但对products的引⽤具有⼆义性,因为MySQL不知道你引⽤的是products表中的哪个实例。
-- 为解决此问题,使⽤了表别名。products的第⼀次出现为别名p1,第⼆次出现为别名p2。现在可以将这些别名⽤作表名。
--例如,SELECT语句使⽤p1前缀明确地给出所需列的全名。如果不这样,MySQL将返回错误,因为分别存在两个名为prod_id、prod_name的列。MySQL不知道想要的是哪⼀个列(即使它们事实上是同⼀个列)。WHERE(通过匹配p1中的vend_id和p2中的vend_id)⾸先联结两个表,然后按第⼆个表中的prod_id过滤数据,返回所需的数据
⽤⾃联结⽽不⽤⼦查询。⾃联结通常作为外部语句⽤来替代从相同表中检索数据时使⽤的⼦查询语句。
虽然最终的结果是相同的,但有时候处理联结远⽐处理⼦查询快得多。
深入了解 join,依次执行下面三条SQL语句,观察结果
1,不添加where语句
select
p1.prod_id,p1.prod_name,p1.vend_id,
p2.prod_id,p2.prod_name,p2.vend_id
from products as p1,products as p2;
两张表数据的乘积14*14
2,只限定两张表的id相等
select
p1.prod_id,p1.prod_name,p1.vend_id,
p2.prod_id,p2.prod_name,p2.vend_id
from products as p1,products as p2
where p1.vend_id = p2.vend_id;
3,限定p2表prod_id为DTNTR
select
p1.prod_id,p1.prod_name,p1.vend_id,
p2.prod_id,p2.prod_name,p2.vend_id
from products as p1,products as p2
where p1.vend_id = p2.vend_id
and p2.prod_id = 'DTNTR';
4,不展示p2表相关字段
select
p1.prod_id,p1.prod_name,p1.vend_id
from products as p1,products as p2
where p1.vend_id = p2.vend_id
and p2.prod_id = 'DTNTR';
子查询(嵌套查询) 是目前可明确知道的 sql中运行效率最低的一种方式,尽可能不使用嵌套语句。
3,外部联结-left join与right join
许多联结将⼀个表中的⾏与另⼀个表中的⾏相关联。但有时候会需要包含没有关联⾏的那些⾏。
例如,可能需要使⽤联结来完成以下⼯作:
- 对每个客户下了多少订单进⾏计数,包括那些⾄今尚未下订单的客户;(之前联结的结果只保留了表之间有关联的记录,如果订单表中没有客户c的订单记录,那么客户c的信息就不会出现在联结的结果中,也就没法统计所有客户的订单信息)
- 列出所有产品以及订购数量,包括没有⼈订购的产品;
- 计算平均销售规模,包括那些⾄今尚未下订单的客户;
在上述例⼦中,联结包含了那些在相关表中没有关联⾏的⾏。这种类型的联结称为外部联结。
还以之前使用的数据库为例,查询所有客户的订单数据:
-- 内部连接
select customers.cust_id,orders.order_num
from customers,orders
where orders.cust_id = customers.cust_id;
select customers.cust_id,orders.order_num
from customers
join orders
on orders.cust_id = customers.cust_id;
select cust_id from customers;
可以看出客户10002的记录并未出现在内部联结的结果中。
那什么是外部联结呢?
left join : 是以 left join 左侧表为基准,去关联右侧的表进行联结,如果有未关联的数据,那么结果为null
right join :是以 right join 右侧表为基准,去关联左侧的表进行联结,如果有未关联的数据,那么结果为null
-- 以用户表为基准,去关联查询 订单表数据
select customers.cust_id,orders.order_num
from customers
left join orders
on customers.cust_id = orders.cust_id;
select customers.cust_id,orders.order_num
from orders
right join customers
on customers.cust_id = orders.cust_id;
-- 以上两条语句等价
聚集函数也可以⽅便地与其他联结⼀起使⽤。
如果要检索所有客户及每个客户所下的订单数,下⾯使⽤了COUNT()函数的代码可完成此⼯作
包含那些没有任何下订单的客户。
-- 对每个客户下了多少订单进行计数,包括那些至今尚未下订单的客户;
select customers.cust_id,count(orders.order_num) as nums
from customers
left join orders
on customers.cust_id = orders.cust_id
group by customers.cust_id;
注意:
- 保证使⽤正确的联结条件,否则将返回不正确的数据。
- 应该总是提供联结条件,否则会得出笛卡⼉积。
- 在⼀个联结中可以包含多个表,甚⾄对于每个联结可以采⽤不同的联结类型。虽然这样做是合法的,⼀般也很有⽤,但应该在⼀起测试它们前分别测试每个联结。这将使故障排除更为简单
四,UNION联合查询
MySQL也允许执⾏多个查询(多条SELECT语句),并将结果作为单个查询结果集返回。
这些组合查询通常称为并(union)或复合查询(compound query)。
UNION规则
- UNION必须由两条或两条以上的SELECT语句组成,语句之间⽤关键字UNION分隔(因此,如果组合4条SELECT语句,将要使⽤3个UNION关键字)。
- UNION中的每个查询必须包含相同的列、表达式或聚集函数(不过各个列不需要以相同的次序列出)
- 列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含地转换的类型(例如,不同的数值类型或不同的⽇期类型)。
假如需要价格⼩于等于5的所有物品的⼀个列表,⽽且还想包括供应商1001和1002⽣产的所有物品。
-- 先查询第⼀个结果
select vend_id,prod_id,prod_price from products where prod_price <= 5;
-- 再查询第⼆个结果
select vend_id,prod_id,prod_price from products where vend_id in(1001,1002);
--使⽤union将两个sql⼀并执⾏
select vend_id,prod_id,prod_price from products where prod_price <= 5
union
select vend_id,prod_id,prod_price from products where vend_id in(1001,1002);
-- 使⽤union将两个sql⼀并执⾏
-- 这条语句由前⾯的两条SELECT语句组成,语句中⽤UNION关键字分隔。
-- UNION指示MySQL执⾏两条SELECT语句,并把输出组合成单个查询结果集
-- 以下是同样结果,使⽤where的多条件来实现
select vend_id,prod_id,prod_price
from products
where prod_price <= 5
or vend_id in (1001,1002);
在这个简单的例⼦中,使⽤UNION可能⽐使⽤WHERE⼦句更为复杂。
但对于更复杂的过滤条件,或者从多个表(⽽不是单个表)中检索数据的情形,使⽤UNION可能会使处理更简单。
现在思考⼀个问题,上⾯的语句分别返回了⼏条数据?
第⼀条sql返回4⾏,第⼆条sql返回5⾏,那么union返回了⼏⾏?
UNION从查询结果集中⾃动去除了重复的⾏(换句话说,它的⾏为与单条SELECT语句中使⽤多个WHERE⼦句条件⼀样)。
这是UNION的默认⾏为,但是如果需要,可以改变它。如果想返回所有匹配⾏,可使⽤UNION ALL⽽不是UNION
select vend_id,prod_id,prod_price from products where prod_price <= 5 union all select vend_id,prod_id,prod_price from products where vend_id in(1001,1002);
对组合查询结果排序
- SELECT语句的输出⽤ORDER BY⼦句排序。在⽤UNION组合查询时,只能使⽤⼀条ORDER BY⼦句,它必须出现在最后⼀条SELECT语句之后。
- 对于结果集,不存在⽤⼀种⽅式排序⼀部分,⽽⼜⽤另⼀种⽅式排序另⼀部分的情况,因此不允许使⽤多条ORDER BY⼦句。
select vend_id,prod_id,prod_price from products where prod_price <= 5
union
select vend_id,prod_id,prod_price from products where vend_id in(1001,1002)
order by prod_price;
这条UNION在最后⼀条SELECT语句后使⽤了ORDER BY⼦句。
虽然ORDER BY⼦句似乎只是最后⼀条SELECT语句的组成部分,但实际上MySQL将⽤它来排序所有SELECT语句返回的所有结果。
五,SQL进阶练习
内容过多,写在另一篇笔记里啦,传送门<( ̄︶ ̄)↗[GO!]:@&再见萤火虫&【05-数据库_数据库高级_SQL进阶练习(部分习题)】
补充:详解sql_mode
sql_mode是MySQL数据库中的一个环境变量;
定义了mysql应该支持的sql语法,数据校验等;
可以通过 select @@sql_mode; 查看当前数据库使用的sql_mode
1,以查询一个学生的选课数目为例,标准的SQL语句为
select stu.sid,stu.sname,count(sc.cid)
from student as stu
left join sc on sc.sid = stu.sid
group by stu.sid,stu.sname;
2,然而我只想按照一列进行分组怎么办?(出现1055错误)
-- 指定列分组,而不是用全部列
select stu.sid,stu.sname,count(sc.cid)
from student as stu
left join sc on sc.sid = stu.sid
group by stu.sid;
-- ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'chuange.stu.Sname' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
3,查看数据库中的sql_mode
select @@sql_mode;
+-------------------------------------------------------------------------------------------------------------------------------------------+
| @@sql_mode |
+-------------------------------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |
+-------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
ONLY_FULL_GROUP_BY
-- 针对grouo by 聚合操作,如果在select中的列,没有在group by中出现,那么将认为sql不合法
4,修改sql_mode
临时修改(服务器重启后失效)
-- 去除ONLY_FULL_GROUP_BY
set @@sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
修改mysql配置文件 my.cnf(重启后生效)
-- 在my.cnf的[mysqld]的下面去配置
[mysqld]
xxx = xxx
sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
5,总结建议:
- 1. 建议开启,符合SQL标准
- 2. 在MySQL中有一个函数 any_value(filed),允许返回非分组字段(和关闭only_full_group_by模式相同)
-- 使用 any_value 函数
select stu.sid,any_value(stu.sname),any_value(stu.sage),count(sc.cid)
from student as stu
left join sc on sc.sid = stu.sid
group by stu.sid;
6,sql_mode值的含义
六,SQL进阶面试题
传送门<( ̄︶ ̄)↗[GO!]:@&再见萤火虫&【05-数据库_数据库高级_SQL进阶面试题】
七,MySQL事务
事务(Transaction)是由⼀系列对系统中数据进⾏访问与更新的操作所组成的⼀个程序执⾏逻辑单元。
1,事务的语法
1. start transaction;/ begin;
2. commit; 使得当前的修改确认
3. rollback; 使得当前的修改被放弃
2,事务的ACID特性
2.1 原子性(Atomicity)
事务的原⼦性是指事务必须是⼀个原⼦的操作序列单元。事务中包含的各项操作在⼀次执⾏过程中,只允许出现两种状态之⼀。
- 全部执⾏成功
- 全部执⾏失败
事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。
事务执⾏过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发⽣⼀样。
也就是说事务是⼀个不可分割的整体,就像化学中学过的原⼦,是物质构成的基本单位。
2.2 一致性(Consistency)
事务的⼀致性是指事务的执⾏不能破坏数据库数据的完整性和⼀致性,⼀个事务在执⾏之前和执⾏之后,数据库都必须处以⼀致性状态。
⽐如:如果从A账户转账到B账户,不可能因为A账户扣了钱,⽽B账户没有加钱。
2.3 隔离性(Isolation)
事务的隔离性是指在并发环境中,并发的事务是互相隔离的。也就是说,不同的事务并发操作相同的数据时,每个事务都有各⾃完整的数据空间。
⼀个事务内部的操作及使⽤的数据对其它并发事务是隔离的,并发执⾏的各个事务是不能互相⼲扰的。隔离性分4个级别,下⾯会介绍。
2.4 持久性(Duration)
事务的持久性是指事务⼀旦提交后,数据库中的数据必须被永久的保存下来。
即使服务器系统崩溃或服务器宕机等故障。只要数据库重新启动,那么⼀定能够将其恢复到事务成功结束后的状态。
3,事务的并发问题
脏读:读取到了没有提交的数据。事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。
不可重复读:同⼀条命令返回不同的结果集(更新)。事务 A 多次读取同⼀数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同⼀数据时,结果 不⼀致。
幻读:重复查询的过程中,数据就发⽣了量的变化(insert, delete)。
4,事务隔离级别
4种事务隔离级别从上往下,级别越⾼,并发性越差,安全性就越来越⾼。 ⼀般数据默认级别是读以提交或可重复读
4.1 查看当前会话中事务的隔离级别
select @@tx_isolation;
4.2 读未提交(READ_UNCOMMITTED)
读未提交,该隔离级别允许脏读取,其隔离级别是最低的。
换句话说,如果⼀个事务正在处理某⼀数据,并对其进⾏了更新,但同时尚未完成事务,因此还没有提交事务;⽽以此同时,允许另⼀个事务也能够访问该数据。
脏读示例:
在事务A和事务B同时执⾏时可能会出现如下场景:
余额应该为1500元才对。请看T5时间点,事务A此时查询的余额为0,这个数据就是脏数据,他是事务B造成的,很明显是事务没有进⾏隔离造成的。
4.3 读已提交(READ_COMMITTED)
读已提交是不同的事务执⾏的时候只能获取到已经提交的数据。 这样就不会出现上⾯的脏读的情况了。但是在同⼀个事务中执⾏同⼀个读取,结果不⼀致
不可重复读示例
可是解决了脏读问题,但是还是解决不了可重复读问题。
事务A其实除了查询两次以外,其它什么事情都没做,结果钱就从1000变成0了,这就是不可重复读的问题。
4.4 可重复读(REPEATABLE_READ)
可重复读就是保证在事务处理过程中,多次读取同⼀个数据时,该数据的值和事务开始时刻是⼀致的。
事务A执行过程中查询的数据和事务A开始时相同,事务A提交后查询的数据才是事务B修改后的数据。
因此该事务级别限制了不可重复读和脏读,但是有可能出现幻读的数据。
幻读
幻读就是指同样的事务操作,在前后两个时间段内执⾏对同⼀个数据项的读取,可能出现不⼀致的结果。
原本在事务A结束之前,是看不到事务B中新增是数据的,但是进行范围修改后(比如某一列全部加上一个值),又能在本次事务A内查询到新增的数据,所以被称为“诡异的更新事件”
4.5 顺序读(SERIALIZABLE)
顺序读是最严格的事务隔离级别。它要求所有的事务排队顺序执⾏,即事务只能⼀个接⼀个地处理,不能并发。
两个事务可以同时读,但是一个事务A执行更新数据的语句后将会卡住,直到另一事务B提交/回滚后,事务A才能继续进行
5,不同的隔离级别的锁的情况(了解)
- 1. 读未提交(RU): 有⾏级的锁,没有间隙锁。它与RC的区别是能够查询到未提交的数据。
- 2. 读已提交(RC):有⾏级的锁,没有间隙锁,读不到没有提交的数据。
- 3. 可重复读(RR):有⾏级的锁,也有间隙锁,每次读取的数据都是⼀样的,并且没有幻读的情况。
- 4. 序列化(S):有⾏级锁,也有间隙锁,读表的时候,就已经上锁了
6,隐式提交(了解)
- DQL:查询语句
- DML:写操作(添加,删除,修改)
- DDL:定义语句(建库,建表,修改表,索引操作,存储过程,视图)
- DCL:控制语⾔(给⽤户授权,或删除授权)
DDL(Data Define Language):都是隐式提交。
隐式提交:执⾏这种语句相当于执⾏commit; DDL
八,MySQL进阶扩展
1,存储过程
1.1 什么是存储过程?
⽬前使⽤的⼤多数SQL语句都是针对⼀个或多个表的单条语句。并⾮所有操作都这么简单,经常会有⼀个完整的操作需要多条语句 才能完成。
例如以下的情形。
- 为了处理订单,需要核对以保证库存中有相应的物品。
- 如果库存有物品,需要预定以便不将它们再卖给别的⼈, 并减少可⽤的物品数量以反映正确的库存量。
- 库存中没有的物品需要订购,这需要与供应商进⾏某种交互。
执⾏这个处理需要针对许多表的多条MySQL语句。可能需要执⾏的具体语句及其次序也不是固定的。
那么,怎样编写此代码?可以单独编写每条语句,并根据结果有条件地执⾏另外的语句。
在每次需要这个处理时(以及每个需要它的应⽤中)都必须做这些⼯作。
可以创建存储过程
存储过程简单来说,就是为以后的使⽤⽽保存 的⼀条或多条MySQL语句的集合。储存过程是⼀组为了完成特定功能的SQL语句集,经过编译之后存储在数据库中,在需要时直接调⽤。
存储过程就像脚本语⾔中函数定义⼀样。
1.2 为什么要使用存储过程?
优点:
- 可以把⼀些复杂的sql进⾏封装,简化复杂操作
- 保证了数据的完整性,防⽌错误
- 简单的变动只需要更改存储过程的代码即可
- 提⾼性能。因为使⽤存储过程⽐使⽤单独的SQL语句要快。(预先编译)
缺点:
- 存储过程的编写⽐SQL语句复杂
- ⼀般可能还没有创建存储过程的权限,只能调⽤
1.3 实战总结
在电信、银⾏业、⾦融⽅⾯以及国企都普遍使⽤存储过程来熟悉业务逻辑,但在互联⽹中相对较少。
- 业务逻辑不要封装在数据库⾥⾯,应该由应⽤程序(JAVA、Python、PHP)处理。
- 让数据库只做它擅⻓和必须做的,减少数据库资源和性能的消耗。
- 维护困难,⼤量业务逻辑封装在存储过程中,造成业务逻辑很难剥离出来。动A影响B。
- ⼈员也难招聘,因为既懂存储过程,⼜懂业务的⼈少。使⽤困难。
1.4 存储过程操作
创建存储过程
\d // 修改MySQL默认的语句结尾符 ; ,改为 // 。(因为;在存储过程中有别的用途)
create procedure 创建语句
BEGIN和END语句⽤来限定存储过程体
-- 定义存储过程
\d //
create procedure p1()
begin
set @i=10;
while @i<90 do
insert into users values(null,concat('user:',@i),@i,0);
set @i=@i+1;
end while;
end;
//
执行存储
call p1()
查看存储过程
show create procedure p1\G
删除存储过程
drop procedure p1
2,触发器
MySQL语句在需要时被执⾏,存储过程也是如此。
但是,如果你想要某条语句(或某些语句)在事件发⽣时⾃动执⾏,怎么办呢?
例如:
- 每当增加⼀个顾客到某个数据库表时,都检查其电话号码格式是否正确;
- 每当订购⼀个产品时,都从库存数量中减去订购的数量;
- ⽆论何时删除⼀⾏,都在某个存档表中保留⼀个副本。
- 所有这些例⼦的共同之处是它们都需要在某个表发⽣更改时⾃动处理。这确切地说就是触发器。
2.1 触发器的定义
触发器是MySQL响应写操作(增、删、改)⽽⾃动执⾏的⼀条或⼀组定义在BEGIN和END之间的MySQL语句
或可理解为:提前定义好⼀个或⼀组操作,在指定的SQL操作前或后来触发指定的SQL⾃动执⾏
触发器就像是JavaScript中的事件⼀样
举例: 定义⼀个update语句,在向某个表中执⾏insert添加语句时来触发执⾏,就可以使⽤触发器
2.2 触发器语法
CREATE TRIGGER trigger_name trigger_time trigger_event
ON tbl_name FOR EACH ROW trigger_stmt
说明:
# trigger_name:触发器名称
# trigger_time:触发时间,可取值:BEFORE或AFTER
# trigger_event:触发事件,可取值:INSERT、UPDATE或DELETE。
# tb1_name:指定在哪个表上
# trigger_stmt:触发处理SQL语句。
-- 查看所有的 触发器
show triggers\G;
-- 删除触发器
drop trigger trigger_name;
2.3 触发器Demo
-- 创建⼀个删除的触发器,在users表中删除数据之前,往del_users表中添加⼀个数据
tips:
在INSERT触发器代码内,可引⽤⼀个名为NEW的虚拟表,访问被 插⼊的⾏;
在DELETE触发器代码内,可以引⽤⼀个名为OLD的虚拟表,访问被删除的⾏;
- OLD中的值全都是只读的,不能更新。
- 在AFTER DELETE的触发器中⽆法获取OLD虚拟表
在UPDATE触发器代码中
- 可以引⽤⼀个名为OLD的虚拟表访问更新以前的值
- 可以引⽤⼀个名为NEW的虚拟表访问新 更新的值;
1,复制一个已经存在的表结构
create table del_users like users;
2,创建 删除触发器
注意在创建删除触发器时,只能在删除之前(必须是before)才能获取到old(之前的)数据
\d //
create trigger deluser before delete on users for each row
begin
insert into del_users values(old.id,old.name,old.age,old.account);
end;
//
\d ;
其中的old是删除时引用的一个虚拟表,详见上方tips
3,删除users表中的数据去实验
2.4 练习
⽤触发器来实现数据的统计
-- 1.创建⼀个表, users_count ⾥⾯有⼀个 num的字段 初始值为0或者是你当前users表中的count
-- 2,给users表创建⼀个触发器
-- 当给users表中执⾏insert添加数据之后,就让users_count⾥⾯num+1,
-- 当users表中的数据删除时,就让users_count⾥⾯num-1,
-- 想要统计users表中的数据总数时,直接查看 users_count
3,视图
3.1 什么是视图?
- 视图是虚拟的表。与包含数据的表不⼀样,视图只包含使⽤时动态检索数据的查询。
- 视图仅仅是⽤来查看存储在别处的数据的⼀种设施或⽅法。
- 视图本身不包含数据,因此它们返回的数据是从其他表中检索出来的。
- 在添加或更改这些表中的数据时,视图将返回改变过的数据。
- 因为视图不包含数据,所以每次使⽤视图时,都必须处理查询执⾏时所需的任⼀个检索。
- 如果你⽤多个联结和过滤创建了复杂的视图或者嵌套了视图,可能会发现性能下降得很厉害。
3.2 视图的作用
- 1. 重⽤SQL语句。
- 2. 简化复杂的SQL操作。在编写查询后,可以⽅便地重⽤它⽽不必知道它的基本查询细节。
- 3. 使⽤表的组成部分⽽不是整个表。
- 4. 保护数据。可以给⽤户授予表的特定部分的访问权限⽽不是整个表的访问权限。
- 5. 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。
- 6. 注意:视图不能索引,也不能有关联的触发器或默认值。
3.3 视图的基础用法
-- 创建视图:
create view v_users as select id,name,age from users where age >= 25 and age <= 35;
Query OK, 0 rows affected (0.00 sec)
--view视图的帮助信息:
mysql> ? view
ALTER VIEW
CREATE VIEW
DROP VIEW
-- 查看当前库中所有的视图
show tables; --可以查看到所有的表和视图
show table status where comment='view'; --只查看当前库中的所有视图
-- 删除视图v_t1:
mysql> drop view v_t1;
不想展示所有数据(比如这里的accont),所以选择创建视图
九,MySQL索引
1,MySQL索引与SQL优化索引的概述与分类
1.1 什么是索引
索引类似图书的目录索引,可以提高数据检索的效率,降低数据库的IO成本。
MySQL官方对索引的定义为:
- 索引(Index)是帮助MySQL高效获取数据的数据结构。
- 我们可以简单理解为:快速查找排好序的一种数据结构。
1.2 索引分类
【效率从高到低】
1,主键索引
即主索引,根据主键建立索引,不允许重复,不允许空值;
2,唯一索引
用来建立索引的列的值必须是唯一的,允许空值
3,普通索引
用表中的普通列构建的索引,没有任何限制
4,全文索引
用大文本对象的列构建的索引。(比如用文章作为索引,可以将文章分为若干词,通过词建立索引,这样可以搜索词就能快速找到文章)
- MYSQL5.6之前的版本中,全文索引只能用于MyISAM存储引擎
- MYSQL5.6及以后的版本,MyISAM 和InnoDB均支持全文索引
- 在之前的MySQL 中,全文索引只对英文有用,目前对中文还不支持(MYSQL8之后支持)
5,组合索引
用多个列组合构建的索引,这多个列中的值不允许有空值
组合索引的最左原则:
2,MySQL索引原理索引与B+Tree
只有memory(内存)存储引擎⽀持哈希索引,哈希索引⽤索引列的值计算该值的hashCode,然后在hashCode相应的位置存执该值所在⾏数据的物理位置,因为使⽤散列算法,因此访问速度⾮常快,但是⼀个值只能对应⼀个hashCode,⽽且是散列的分布⽅式,因此哈希索引不⽀持范围查找和排序的功能
正常情况下,如果不指定索引的类型,那么⼀般是指B+Tree索引(或者B+Tree索引)。
存储引擎以不同的⽅式使⽤B+Tree索引。性能也各有不同,但是InnoDB按照原数据格式进⾏存储。
2.1 B树简介
当数据存在内存中,红黑树效率非常高,但是文件系统和数据库都是存在硬盘上的,如果数据量大的话,不一定能一次性 加载到内存。所以一棵树都无法一次性加载进内存,又如何谈查找。
因此就出现了专为磁盘等存储设备而设计的一种平衡多路查找树,也就是B树
- 与红黑树相比,在相同的的节点的情况下,一颗B/B+树的高度远远小于红黑树的高度
- B树即平衡查找树,一般理解为平衡多路查找树,也称为B-树、B_ 树。
- B树是一种自平衡树状数据结构,一般较多用在存储系统上,比如数据库或文件系统。
通过上⾯的三个案例就发现了⼏个问题
- 1.查找7和10 所⽤步数不⼀样,就意味着时间不同,效率就不均衡,有的可能快,有的可能慢
- 2.在进⾏范围查找时,需要反复的返回上⼀节点,在进⼊下⼀节点,这种情况其实是树的遍历,叫做中序遍历,消耗了时间
- 3.还有最重要的⼀点,在B树中,由于每⼀个节点就是⼀⾏数据,那么就是⼀次IO读区的节点更少。
- 在计算机中,所有与空间相关的东⻄都是按照块(block)进⾏存取和操作的
- 每次读取都意味着⼀次I/O,假设计算机中每个块的⼤⼩为4K,⾏的⼤⼩为1k,索引的⼤⼩为0.06K
- 如果需要寻址遍历的次数多,就意味着更多的IO
2.2 B+树简介
这里有篇博客简单介绍B+ 树,个人感觉比较比较清晰易懂,传送门<( ̄︶ ̄)↗[GO!]@zhifeng687【b+树图文详解】
2.3 B树与B+树对比
1磁盘读写代价更低
- B树的数据和索引都在同一个节点上,那么每个块中包含的索引是少量的,如果想要取出比较深层的数据,意味着要读取更多的块,才能得到想要的索引和数据,那么就增加了IO次数
- B+树中每个块能存储的索引是B树的很多倍,那么获取比较深层的数据,也只需要读取少量的块就可以,那么就减少了磁盘的IO次数
2.随机IO的次数更少
- 随机I/O是指读写操作时间连续,但访问地址不连续,时长约为10ms。
- 顺序I/O是指读取和写入操作基于逻辑块逐个连续访问来自相邻地址的数据,时长约为0.1ms
- 在相同情况下,B树要进行更多的随机IO,而B+树需要更多的顺序IO,因此B+树,效率也更快
3.查询速度更稳定
- 由于B+Tree非叶子节点不存储数据(data) ,因此所有的数据都要查询至叶子节点,而叶子节点的
- 高度都是相同的,因此所有数据的查询速度都是一样的。
2.4 聚簇索引和非聚簇索引
索引又分为聚簇索引和非聚簇索引两种。
- 在索引的分类中,我们可以按照索引的键是否为主键来分为“主索引”和“辅助索引”
- 使用主键键值建立的索引称为“主索引”,其它的称为“辅助索引”。
- 因此主索引只能有一个,辅助索引可以有很多个。
以上关于索引原理和聚簇与非聚簇索引都是以InnoDB表引擎为基础
- 我们知道InnoDB中索引即数据,也就是聚簇索引的那棵B+树的叶⼦节点中已经把所有完整的数据都包含了,⽽MyISAM的索引⽅案虽然也使⽤树形结构,但是却将索引和数据分开存储:也就是把索引信息单独存到⼀个⽂件中,这个⽂件称为索引⽂件
- MyISAM会单独为表的主键创建⼀个索引,只不过在索引的叶⼦节点中存储的不是完整的数据记录,⽽是主键值 + ⾏号的组合。也就是先通过索引找到对应的⾏号,再通过⾏号去找对应的记录!其它⾮主键索引也是⼀样的,这种情况我们称为’回⾏’。所以在MyISAM中所有的索引都是⾮聚簇索引,也叫⼆级索引
2.5 MyISAM和InnoDB的区别
数据存储⽅式:
- InnoDB由两种⽂件组成,表结构,数据和索引
- MyISAM由三种⽂件组成,表结构、数据、索引
索引的⽅式:
- 索引的底层都是基于B+Tree的数据结构建⽴
- InnoDB中主键索引为聚簇索引,辅助索引是⾮聚簇索引
- MyISAM中数据和索引存在不同的⽂件中,因此都是⾮聚簇索引
事务的⽀持:
- InnoDB⽀持事务
- MyISAM不⽀持事务
2.6 总结
- 数据库的索引就是为了提高数据检索速度
- 而数据库的索引就是基于B+ Tree的数据结构实现的
- 在InnoDB中主键是聚簇索引而辅助索弓|是非聚簇索引
- 在MyISAM中主键索引|和辅助索引都是非聚簇索引
十,MySQL慢查询与SQL优化
1,什么是慢查询
MySQL的慢查询,全名是慢查询日志,是MySQL提供的一种日志记录,用来记录在MySQI中响应时间超过阀值的语句。
默认情况下,MySQL数据库并不启动慢查询日志,需要手动来设置这个参数。
如果不是调优需要的话,一般不建议启动该参数,开启慢查询日志会或多或少带来一定的性能影响。
2,慢查询配置
3,测试
1,创建表,插入测试数据
2,查看慢查询日志
3,Explain语句
简介:
- 一条 查询语句在经过MySQL查询优化器的各种基于成本和规则的优化会后生成一个所谓的执行计划
- 这个执行计划展示了接下来具体执行查询的方式,比如多表连接的顺序是什么,对于每个表采用什么访问方法来具体执行查询等等。
- MySQL为我们提供了EXPLAIN语句来帮助我们查看某个语句的具体执行计划。
参数:
- id:在⼀个⼤的查询语句中每个SELECT关键字都对应⼀个唯⼀的id
- select_type: SELECT关键字对应的那个查询的类型
- table: 表名
- partitions: 匹配的分区信息
- type: 针对单表的访问⽅法
- possible_keys: 可能⽤到的索引
- key: 实际上使⽤的索引
- key_len: 实际使⽤到的索引⻓度
- ref: 当使⽤索引列等值查询时,与索引列进⾏等值匹配的对象信息
- rows: 预估的需要读取的记录条数
- filtered: 某个表经过搜索条件过滤后剩余记录条数的百分⽐
- Extra: ⼀些额外的信息
使用Explain分析SQL语句
根据name进行查询
根据主键id进行索引
为name添加普通索引,再次执行
Explain分析
大家看到,索引能给数据检索提高的效率非常明显
那么是否意味着我们只要尽可能多的去建立索引就可以了呢?
- 每建立一个索引都会建立一棵B+树,并且需要维护,这是很费性能和存储空间的。
4,索引使用
4.1 适当建立索引
- 1.创建并使用自增数字来建立主键索引
- 2.经常作为where条件的字段建立索引.
- 3.添加索弓|的字段尽可能的保持唯一性
- 4.可考虑使用联合索引并进行索引覆盖
4.2 合理使用索引
1,不要在列上使用函数和进行运算
2,隐式转换(数据类型不一致)可能影响索引失效
3,like语句的索引失效问题
4.3 复合索引的使用
1,多个单列索引并不是最佳选择
MySQL只能使用一个索引,会从多个索引中选择个限制最为严格的索引,因此,为多个列创建 单列索引,并不能提高MySQL的查询性能。
可以使用复合索弓|保证列都被索引覆盖
2,复合索引的最左前缀原则
查询条件中使用了复合索引的第一个字段,索引被使用。 因此,在复合索引中索弓 |列的顺序至关重要。
如果不是按照索引的最左列开始查找,则无法使用索引。
3,尽可能达成索引覆盖
如果一个索引包含所有需要的查询的字段的值,直接根据索引的查询结果返回数据,而无需读表,能够极大的提高性能。
因此,可以定义一个让索引包含的额外的列,即使这个列对于索引而言是无用的。
5,总结
SQL语句的优化
- 1.避免嵌套语句(子查询)
- 2.避免多表查询(复杂查询简单化)
索引优化
- 1.适当建立索引
- 2.合理使用索引
章节汇总在这里(づ ̄3 ̄)づ╭❤~@&再见萤火虫&【05-数据库】
对学习Java感兴趣的同学欢迎加入QQ学习交流群:1126298731
有问题欢迎提问,大家一起在学习Java的路上打怪升级!(o゜▽゜)o☆[BINGO!]