MySQL基础

165 阅读6分钟

基础

MySQL不区分大小写
不同语句用";"分割,多个换行,间隔在sql执行时被忽略
string,日期必须用""/''封好
选择数据库:双击/USE+数据库名字
别写错单词,报错信息会比较模糊,不好找问题

第一部分:数据类型,增删改查,分组,子查询

通用

  1. 取别名
    给列取别名,使用AS (points+10)*100 AS discount_factor 给表取别名,空格就成 customer c
  2. 去重 DISTINCT

数据类型

  1. string types char fixed-length
    varchar <64kb
    Mediumtext <16MB
    longtext <4GB
  2. numeric types
    整数
    tinyint [-128,127]
    mysql integer types
    小数
    DECIMAL(p,s) 小数点后有固定位数的数,用于$,¥...
    FLOAT,DOUBLE 用于科学计算,近似值,不是准确值
  3. boolean true,false
  4. 可枚举
    ENUM('S','L','M') 不好用,尽量避免
    更好的方法是单独建立一张规格表
  5. SET
    SET(...) 同ENUM,尽量避免,有更好的替代方案
  6. date and time types
    DATE
    TIME
    DATETIME 8b
    TIMESTAMP √ 4个字节,存2038年前
    YEAR
  7. blob types 图像,视频,pdf,word文件....
    tinyblob 255b
    blob 65kb √
    mediumblob 16m
    longblob 4g
    把图像从数据库提取出来,比从文件系统慢
  8. JSON JSON 8.0.15以上版本才支持
  SET properties='{"key1":"value1","key2":"value2"...}'
  JSON_OBJECT(
    'weight':10,
    'dimensions':JSON_ARRAY(1,2,3)
  )
  JSON_EXTRACT(properties,'$.weight')
  properties->'$.weight'
  properties->>'$.manufacturer.name'  --获取去掉""的sony
  SET properties=JSON_SET(
    properties,
    '$.weight',20
  )
  JSON_REMOVE(
    properties,
    '$.age'
  )
  1. spatial type 存储地理地区...

SELECT语句(查)

格式:SELECT 列名 FROM 表名 (WHERE 条件) (ORDER BY 列名)
WHERE,ORDER BY语句是可选的

WHERE语句(按条件筛选)

  1. 简单条件
    > , >= , < , <= , = , != , <>
    WHERE order_data>='1999-01-01'
  2. 复杂条件
    AND, OR, NOT, IN, BETWEEN, ALL, SOME, ANY, EXISTS, LIKE, REGEXP
    IS NULL, IS NOT NULL
    优先级:AND>OR
    BETWEEN 包含两端的值
    EXISTS 类似IN,不返回具体结果,返回true/false,大表查询比IN更快
    NOT 和其他条件搭配使用,NOT LIKE,NOT EXISTS... LIKE '%'匹配任何个数的任何字符,'_'匹配任何单个字符 REGEXP ^, $, |, [], - ...

ORDER语句(排序)

ORDER BY 字段/表达式 DESC/ASC
默认按升序排列
字段/表达式可以有多个,先按第一个字段/表达式排,再按第二个....
MySQL排序字段可以不在选择子句的字段中,也可以是别名
ORDER BY quantity*unit_price DESC

LIMIT语句(限制)

LIMIT 6,3 --跳过前6条数据,获取3条数据

INSERT(插入)

格式1: 表中的每个字段都要按顺序填
INSERT INTO customers VALUES (DEFAULT,..., NULL);
格式2: 只要按要填的字段的顺序填入数据即可
INSERT INTO customer(字段1,字段2) VALUES (..),(...)

创建新表

复制出来的新表,会忽略旧表主键,自增字段属性,默认值还是保留的

  1. 从旧表创建全新的表 CREATE TABLE orders_archived AS
    SELECT * FROM orders --子查询
  2. 将子查询结果添加到有字段名的空表
    INSERT INTO orders_archived
    SELECT * from orders

update(更新)

更新一条或多条数据,取决于where查询能够找到多少条语句

  UPDATE invoices    
  SET payment_total=10,payment_date='2019-03-01'     
  WHERE invoices_id=1    
  UPDATE invoices   
  SET    
    payment_total=invoice_total*0.5,   
    payment_date=due_date   
  WHERE client_id IN      --IN在此处使用真的绝了!!!!  
    (SELECT client_id    
    FROM clients     
    WHERE state IN ('ca','ny'))   

DELETE(删)

DELETE FROM invoices WHERE invoice_id = (SELECT...)

GROUP BY (分组)

顺序: SELECT,FROM,WHERE 之后, ORDER BY之前
一般来讲,当选择语句中有聚合函数,对数据进行分组按selete语句中的所有字段

HAVING (按条件筛选)

WHERE 筛选分组前的数据,使用的字段可以不在SELECT语句里面 HAVING 筛选分组后的数据,使用的字段必须在SELECT语句里面

WITH ROLLUP

用于计算聚合值的列 的总和, 计算结果添加到数据的最后一行
使用rollup运算符,不能在GROUP BY语句中使用列别名,需要使用真实的名字
GROUP BY state, city WITH ROLLUP 计算每个分组的聚合数据,并计算所有分组总的聚合数据

  • 只在MySQL中有,不是标准的SQL语句
  SELECT    
    pm.name AS payment_method,   
    SUM(amount) AS total    
  FROM payments p   
  JOIN payment_methods pm   
    ON p.payment_method=pm.payment_method_id   
  GROUP BY payment_method WITH ROLLUP   

子查询

子查询可用于WHERE,FROM,SELECT等等语句里面

  SELECT *   
  FROM employees e   
  WHERE salary > (  
    SELECT AVG(salary)
    FROM employees
    WHERE office_id=e.office_id
  )
  
  SELECT *
  FROM products p
  WHERE NOT EXISTS (
    SELECT product_id
    FROM order_items
    WHERE product_id=p.product_id
  )
  
  SELECT 
    invoice_id,invoice_total,
    (SELECT AVG(invoice_total)
      FROM invoices) AS invoice_average,
    invoice_total-(SELECT invoice_average) AS difference
  FROM invoices

第二部分 连接

连接列:内连接,外连接(左连接,右连接),交叉连接
连接行:UNION

内连接

INNER JOIN 可以简写成JOIN

  SELECT order_id,o.customer_id,first_name    
  FROM orders o    
  JOIN customers c    
  ON o.customer_id=c.customer_id; 

注意事项:

  1. 多张表有相同字段时,需要给字段加上前缀
  2. 给表取了别名,其他所有地方()一定要使用这个别名
  3. 连接条件可以是复合条件
    ON oi.order_id=oin.order_id AND oi.product_id=oin.product_id
  4. 连接的两张表可以来自不同/相同的数据库,可以是同一张表.还可以连接多张表

跨数据库连接

  SELECT * 
  FROM order_item oi
  JOIN sql_inventory.products p
    ON oi.product_id=p.product_id

注意事项:
当前使用的数据库对应的表,可以不加前缀.但是,当前的数据库是会变的,最好都加上.

自连接

-- 找所有人的上级

  SELECT 
    e.employee_id,
    e.first_name,
    m.first_name AS manager
  FROM employees e
  JOIN employees m
    ON e.reports_to=m.employee_id;

注意事项:

  1. 需要取不同的别名
  2. 选中的相同的字段最好取个别名,一张表里面有两个重复字段有些奇怪

多表连接

  SELECT 
    o.order_id,o.order_data,
    c.first_name,c.last_name,
    os.name AS status
  FROM order o
  JOIN customers c
    ON o.customer_id=c.customer_id
  JOIN order_statuses os
    ON o.statuus=os.order_status_id

外连接

使用内连接,只能看到满足条件的results,看不到不满足条件的
如下查询,只能看到下了订单的用户,看不到没下订单的用户

SELECT     
  c.customer_id,c.first_name,    
  o.order_id    
FROM customers c    
JOIN orders o     
  ON c.customer_id=o.customer_id  --c中不满足此条件的数据不会被返回   
ORDER BY c.customer_id   

左连接

内连接:只有on的条件满足的result,才会被返回
左连接:无论on的条件是否满足,左表的所有记录都会被返回
右连接:无论on的条件是否满足,右表的所有记录都会被返回
左连右连调换表的顺序是一样的,为提高可读性,最好不要混着用.用左连即可
同内连接使用差不多,也可以多表连接,跨数据库连接...

SELECT 
  c.customer_id,c.first_name,
  o.order_id
FROM customers c
LEFT JOIN orders o
  ON c.customer_id=o.customer_id  
ORDER BY c.customer_id

USING语句

只能在连接条件不同表的字段名一样时代替ON使用
USING (customer_id) == ON c.customer_id=o.customer_id
可以有多个条件
USING(order_id,product_id)

交叉连接

应用场景:尺寸和颜色
表1的每个record和表2的每个record相连接,会有rs1*rs2个rs
SELECT *
FROM customers c
CROSS JOIN products p

UNION

两个片段的字段必须相同.表头由第一个片段决定

SELECT    
  order_id,order_date,'Archived' AS status     
FROM orders     
WHERE order_data<'2021-01-01'   
UNION    
SELECT     
  order_id,order_date,'Archived' AS status    
FROM orders    
WHERE order_data<'2021-01-01'     

第三部分:MySQL内置函数

LAST_INSERT_ID() 获取最新插入的record的ID

聚合函数

MAX() , MIN() , AVG() , SUM() , COUNT()

  1. 数值,日期,字符串都可以用
  2. 只运行非空值,如果需要所有记录count(*)
  3. ()里面可以说字段名,也可以是表达式 COUNT(DISTINCR client_id)

numeric function

ROUND(num,n) 四舍五入,保留n位小数
TRUNCATE(num,n) 保留n位小数
CEILING(num)
FLOOR(num)
ABS(num)
RAND()

string function

LENGTH(str)
UPPER(str)
LOWER(str)
LTRIM(str),RTRIM(str),TRIM(str)
LEFT(str,n) 从左边拿n个字符
SUBSTRING(str,start,length)
LOCATE(char,str)
REPLACE(str,substr,substr1)
CONCAT(str1,str2)

date function

NOW() =CURDATE()+CURTIME()
CURDATE()
CURTIME()
YEAR(NOW()),MONTH,DAY,HOUR,SECOND... --返回数字
DAYNAME(NOW()),MONTHNAME(NOW()) --返回字符串
EXTRACT(YEAR FROM NOW())
DATE_FROMAT(NOW(),'%M %D %Y') --日期格式化函数,为了给人看.具体规则查文档
DATA_ADD(NOW(),INTERVAL 1 YEAR)
DATEDIFF(date1,date2) --返回天的差异 TIME_TO_SEC('09:00')-TIME_TO_SEC('09:02')

其他

IFNULL --如果第一个参数为空,用第二个参数替代
COALESCE --比IFNULL更灵活,第一个参数为NULL,用第二个参数替代,第二个为空,第三个替代...
IF(条件,条件满足,条件不满足)
CASE WHEN 相当于多级IF

  IFNULL(shipper_id,'NOT assigned') AS shipper 
  
  COALESCE(shipper_id,comments,'NOT assigned') 
  
  IF(YEAR(order_date)=YEAR(NOW()),'Active','Archived') AS category
  
  CASE
    WHEN YEAR(order_date)=YEAR(NOW()) THEN 'Active'
    WHEN ....
    ELSE 'Future'
  END

第四部分:高级用法

视图,存储过程,函数,触发器,事件,事务

VIEW(视图)

CREATE OR REPLACE VIEW table_name AS SELECT...

可更新视图

没有DISTINCT 没有Aggregate Functions (MIN,MAX,SUM...) 没有GROUP BY/HAVING 没有UNION

WITH CHECK OPTION 选项

如果操作导致可更新视图某些results消失,会给出报错提示

STORED PROCEDURE(存储过程)

目的:管理SQL,更快执行,有利于数据安全

  1. 新增存储过程. 可以从头开始写,也可以利用mysql提供的存储过程
  DELIMITER $$
  CREATE PROCEDURE `get_invoices_with_balance`(
    client_id INT(11)
  )
  BEGIN
    --设置默认值是CA
    IF state IS NULL THEN
      SET state='CA'
    END IF;
    --会执行的语句
    SELECT *
    FROM invoices i
    WHERE i.client_id=client_id;
    
    --设置默认值是全选
    SELECT * FROM clients c
    WHERE c.state=IFNULL(state,c.state)
  END$$
  DELIMITER ;
  1. 删除存储过程
  DROP PROCEDURE IF EXISTS get_clients;

参数校验

在用户输入信息的时候,就应该校验.而不是等到数据库校验

  BEGIN
    IF payment_amount<= 0 THEN
      SIGNAL SQLSTATE '22003'
        SET MESSAGE_TEXT='Invalid payment amount'
    END IF;
  ...

输出参数

OUT
可以有多个输出参数,函数只有一个返回值

函数

函数只有单一返回值;存储过程,多个返回值...

CREATE FUNCTION get_risk_factor_for_client(
  client_id INT
)
RETURNS INTERGER
--有多个特性可以选择
--DETERMINISTIC
READ SQL DATA
--MODIFIES SQL DATA
BEGIN
  DECLARE risk_factor DECIMAL(9,2) DEFAULT 0;
  DECLARE invoices_total DECIMAL(9,2);
  DECLARE invoices_count INT;

  SELECT COUNT(*),SUM(invoice_total)
  INTO invoice_count,invoices_total
  FROM invoices i
  WHERE i.client_id=client_id;

  SET risk_factor=invoices_total/invoices_count*5;
  RETURN risk_factor;
END

TRIGGER

  DELIMITER $$
  CREATE TRIGGER payments_after_insert()
    AFTER INSERT ON payments
    FOR EACH ROW 
  BEGIN
    UPDATE invoices
    SET payment_total=payment_total+NEW.amount
    WHERE invoice_id=NEW.invoice_id;

    --记录增删改查操作
    INSERT INTO payment_audit
    VALUES (OLD.client_id,OLD.date,OLD.amount,'DELETE',NOW());
  END$$
  DELIMITER ;
  
  
  SHOW TRIGGERS   --查看已有TRIGGERS
  DROP TRIGGER IF EXISTS payments_after_insert;  --删除某个TRIGGER

EVENT(事件)

  SHOW VARIABLES LIKE 'event%';
  SET GLOBAL event_scheduler=ON;
  
  DELIMITER $$
  CREATE EVENT yearly_delete_stale_audit_rows
  ON SCHEDULE
    --AT '2019-05-01'
    EVERY 1 YEAR STARTS '2019-01-01' ENDS '2029-01-01'
  DO BEGIN
    DELETE FROM payments_audit
    WHERE action_date < NOW()-INTERVAL 1 YEAR;
    --DATEADD(NOW(),INTERVAL -1 YEAR)
  END $$
  DELIMITER ;
  
  
  SHOW EVENTS
  DROP EVENT IF EVENT yearly_delete_stale_audit_rows
  AFTER EVENT yearly_delete_stale_audit_rows DISABLE/ENABLE

TRANSACTION(事务)

ACID特性

创建事务

START TRANSACTION; .... --增删改查等等语句 COMMIT; --ROLLBACK 手动撤销

并发

导致的问题:lost updates,dirty reads,non-repeating reads,phantom reads

  1. 两个事务更新同一行,后提交的事务覆盖之前事务所做的修改
  2. 读取未提交事务
  3. 读取相同数据两次,得到不同结果
  4. 有另外一个事务正在执行,影响查询操作的准确性 幻读 解决方案:设置隔离等级
    read committed, read committed, repeatable read, serializable
SHOW VARIABLES LIKE 'transaction_isolation'  --查看隔离等级

-- 设置下一个事务的隔离层级
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 设置全局/会话层面的隔离等级
SET GLOABLE/SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

image.png

死锁

不同事务握住了互相需要的锁,导致事务无法完成
永远无法真正消除,只能减小其可能性

  1. 遵照相同顺序.经常发生死锁的几个事务,参看执行顺序...
  2. 简化事务,缩小事务运行事件

第五部分:数据库设计,索引优化,权限管理

数据库设计

  1. 理解,分析业务需求 查看现有表单,文档,应用程序,电子表格,数据库,收集信息
  2. 构建概念模型 识别业务中实体/事物/概念以及它们之间的关系
    用于不同领域的人交流的
  3. 构建逻辑模型 新增table,字段
  4. 构建实体模型 新增数据类型,默认值,主键, 视图,存储过程,触发器...

conceptual models

工具:microsoft visio,draw.io,lucidCharts
绘制entity relationship

logical models

多对多关系,分离出链接表

physical models

选主键、复合主键、外键,外键级连关系(CASCADE/RESTRICT/NO ACTION)

标准化

  1. 第一范式 每一行的每个单元格都应该有单一值,且不能出现重复列
  2. 第二范式 满足第一范式 每张表都应该只有一个单一实体,表中的每一列都用于描述这个单一实体
  3. 第三范式 满足第二范式 表中的列不应该由表中的其他列派生得到(类似计算公式?)

模型的正向工程

根据模型生成数据库
Database->forwardEngineer
生成数据库后修改表
Database->Synchronize Model...

模型的反向工程

根据数据库生成模型

使用sql语句

创建库/表

  CREATE DATABASE IF NOT EXITS sql_store

  USE sql_store
  DROP TABLE IF EXISTS customers;
  CREATE TABLE customers(
    customer_id INT PRIMARY KEY AUTO_INCREMENT,
    --NOT NULL DEFAULET 0 UNIQUE
  )

修改表

  AFTER TABLE customers
    ADD last_name VARCHAR(50) NOT NULL AFTER first_name,
    MODIFY first_name VARCHAR(55) DEFAULT '',
    DROP points
    DROP FOREIGN KEY fk_orders_customers,
    ADD FOREIGN KEY fk_orders_customers (customer_id)
      REFERENCES customers (customer_id)
      ON UPDATE CASCADE
      ON DELETE NO ACTION
    ;

创建关系

  CREAT TABLE orders(
    order_id     INT PRIMARYKEY,
    customer_id  INT NOT NULL,
    FOREIGN KEY fk_orders_customers (customer_id)
      REFERENCES customers (customer_id)
      ON UPDATE CASCADE
      ON DELETE NO ACTION
  )

字符集和排序规则

SHOW CHARSET --获取所有字符集
default collation 默认排序规则

Schema inspector 鼠标操作不能在数据库级别更改字符集.可以查看..

--DATABASE/TABLE/COLUMN的字符集都可以设置
--设置数据库,表层面的字符集
ALTER/CREATE DATABASE/TABLE  db_name
  CHARACTER SET latin1
--设置列层面的字符集  
first_name VARCHAR(50) CHARACTER SET latin1 NO NULL...

存储引擎

表级别的,多个引擎可以共存在一个库
SHOW ENGINES --查看使用的存储引擎
InnoDB 常用,支持事务
MyISAM 过时,不支持事务、外键

ALTER TABLE customer ENGINE=InnoDB --修改期间用不了表

索引

cluster indexes 主键才有,一个表只能有一个
secondary indexes 外键自动添加,设置的索引也是二级索引.存储主键+次级索引列信息

  EXPLAIN SELECT customer_id FROM customer WHERE points>1000;
  CREATE INDEX idx_state ON customers (state);

  SHOW INDEXES IN customers
  ANALYZE TABLE customers   --重新生成表的统计信息,准确的
  DROP INDEX idx_points ON customers

  --Collation  A 升序,D 降序
  --Cardinality  估计索引中唯一值的数量   ANALYZE TABLE
 
  --type all 全表扫描
  --type index  扫描index.比全表扫描快,不涉及从磁盘读取每个记录

前缀索引

char,varchar,text,blob 为索引列数据类型,会增大存储空间,不利于提高性能
∴不以整体内容作为索引,只包含前几个字符

  CREATE INDEX idx_lastname ON customers (last_name(20));

  SELECT COUNT(DISTINCT LEFT(last_name,1)) FROM customers;

全文索引

  CREATE FULLTEXT INDEX idx_title_body ON posts (title,body);
  SELECT *,MATCH(title,body) AGAINST ('react redux')   --获取相关度排序
  FROM posts
  WHERE MATCH(title,body) AGAINST ('react redux');
  AGAINST ('react -redux +form' IN BOOLEAN MOOD);
  AGAINST ('"handling a form"' IN BOOLEAN MOOD);

复合索引

  CREATE INDEX idx_state_poinrs ON customers (state,points);

复合索引顺序

  1. put the most frequently used columns first
  2. put the columns with a higher cardinality(基数) first
  3. put your queries into account 第2条不一定100%对,取决于查询条件,范围还是确确的数

解决索引失效

  1. isolate your column
  2. 新增索引 或者 修改查询语句 例如:or=>union

利用索引排序

(a,b)=>a, a,b, a DESC,b DESC
对于以a,b两个字段组成的复合索引,用于按a升/降排序, a,b同升/降排序,
利用索引排序,性能会得到优化

利用索引优化性

  1. WHERE,ORDER BY,SELECT语句,能利用索引的尽量利用索引
  2. 创建新索引前,看看之前的索引是否可以利用.避免duplicate indexes,redundant indexes

用户管理

  --创建新用户
  CREATE USER john@127.0.0.1  IDENTIFIED BY '123456';
  --查看用户
  SELECT * FROM mysql.user
  --删除用户
  DROP USER bob@'%codewithmosh.com'

  --修改密码
  SET PASSWORD FOR john='12456';  --改别人的
  SET PASSWORD ='Q123342';  --改自己的

权限管理

  1. web/desktop application
CREATE USER moon_app IDENTIFIED BY '1234';
GRANT SELECT,INSERT,DELETE,UPDETE,EXECUTE
  ON sql_store.*
  TO moon_app;
  1. admin
GRANT ALL ON *.* TO john;
--查看权限
SHOW GRANTS FOR john
--撤销授权
REVOKE CREATE VIEW ON sql_store.* FROM moon_app