解析Mysql

sql的执行顺序:

  • 1 FROM <left_table>
  • 2 WHERE <where_condition>
  • 3 GROUP BY <group_by_list>
  • 4 HAVING <having_condition>
  • 5 SELECT
  • 6 ORDER BY <order_by_condition>
  • 7 LIMIT <limit_number>
  • 8 DISTINCT <select_list>

字段类型的取值范围

  • 1 bigint 从 -9223372036854775808 到 9223372036854775807 占 8 个字节
  • 2 int 从-2,147,483,648 到 2,147,483,647 占 4 个字节
  • 3 mediumint 从-8388608到8388607,无符号的范围是0到16777215。 一位大小为3个字节
  • 4 smallint 从-2^15 (-32,768) 到 2^15 - 1 (32,767) 的整型数据,无符号的范围是0到65535。一位大小占 2 个字节
  • 5 tinyint 从-128--127,0~255。一位大小为 1 字节
  • 6 varchar
varchar  
字符类型若为gbk,每个字符最多占2个字节,最大长度不能超过32766;
字符类型若为utf8,每个字符最多占3个字节,最大长度不能超过21845
一个字母或数字占用1个字节
varchar  (30)  30--90字节
1个汉字占3个字节。
批量插入多条数据
insert into [tablename] (field1,field2...) values (value1,value12...)
批量插入语句
func insertData() {
    data := []map[string]string{
        {"name": "sos", "email": "87262420@qq.com"},
        {"name": "腻子", "email": "297387990@qq.com"},
        {"name": "领导", "email": "sosmyheart@163.com"},
    }

    sql := "insert into users(name, email) values"
    var valuesList = []interface{}{}

    for index, v := range data{
        if index == len(data)-1{
            sql += "(?,?)"
        }else{
            sql += "(?,?), "
        }
        valuesList = append(valuesList, v["name"], v["email"])
    }
    res, _ := db.Exec(sql, valuesList...) //批量数据!
    mainId,_ := res.LastInsertId() //每插入一条数据就返回一个表的主键id
}

UNION

  • 1 UNION ALL 会导致查询的数据很多重复的
  • 2 UNION 会做去重处理
  • 3 联合查询的时候,字段列数要一致,而且字段的类型也要一致!
表1
A    B
a    1
b    0
c    3
d    0
e    2

表2
A    B
c    0
e    4

合并两个表除去重复的数据(以表2的数据为主),得到以下表
A    B
a    1
b    0
c    0
d    0
e    4

select A,B from 表1 where A not in(select A from 表2)
union
select A,B from 表2

group by / order by 顺序问题

  • 1 先分组后排序
  • 2 ORDER BY时不要使用SELECT *,只查需要的字段。

having

  • 1 检索范围不一致,它是在前者查询完毕的基础上再进行检索过滤!用于对已分组的数据进行过滤
  • 2 跟where不是一个范围,having是在where条件之上进行筛选。
where
select * from table group by 字段 having 字段>10

having
select * from table having sum(字段)>100

联合索引--最左匹配原则

  • 1 最左匹配原则是针对联合索引来的,索引的底层是一颗B+树,构建一颗B+树只能根据一个值来构建,当索引类型为联合索引时,数据库会依据联合索引最左的字段来构建B+树,也叫最左匹配原则,联合索引其实可以减少开销。

  • 2 最左优先。以最左边的为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between、like)就会停止匹配。

  • 3 查看是不是用到索引可以使用:EXPLAIN

EXPLAIN SELECT * from test ORDER BY a,b,c desc limit 10
  • 最左匹配原则,可以是在where 或者 group by, order by 之后带的字段都可以走索引。
image.png
  • 4 根据type字段来判断是否使用索引,下面是性能的大小排列,ALL是最弱的!
  • 5 index 类型的查询虽然不是全表扫描, 但是它扫描了所有的索引, 因此比 ALL 类型的稍快.
ALL < index < range ~ index_merge < ref < eq_ref < const < system
  • 6 索引列越多,通过索引筛选出的数据越少。有1000W条数据的表,有如下SQL: select from table where col1=1 and col2=2 and col3=3,假设假设每个条件可以筛选出10%的数据,如果只有单值索引,那么通过该索引能筛选出1000W10%=100w条数据,然后再回表从100w条数据中找到符合col2=2 and col3=3的数据,然后再排序,再分页;如果是联合索引,通过索引筛选1000w10%10%10%=1w,效率提升可想而知

  • 7 联合索引就是按照第一列进行排序,然后第一列排好序的基础上再对第二列进行排序,以此类推。如果没有第一列直接访问第二列,第二列肯定是无序的,直接访问后面的列就用不到索引了。

低粒度的锁--RWMutex (读写锁)

  • 1 读写锁--可以并发多个读操作,但是写是互斥的,当goroutine进行写操作时,其他goroutine既不能读也不能写,都是阻塞!

  • 2 多个goroutine并发获取读锁的时候,有goroutine要获取写锁,这时候写锁的线程是阻塞的,后面如果还有goroutine想要获取读锁也是阻塞的~!

  • 3 当一个goroutine获取写锁后,其他想要获取读锁和写锁的都会阻塞

  • 4 读锁是可重入锁,同一个goroutine可以多次获取读锁。同理,当一个线程获取到了写锁后尝试获取读锁,会造成死锁错误。

  • 5 读写锁跟互斥锁的区别在于:锁的粒度减少,读写锁可以让多个goroutine获取共享资源时同时获取读锁。而互斥锁在并发获取共享资源的时候,只能让一个goroutine获取读锁,其他goroutine阻塞。影响性能!

sync.Mutex的操作

  • 1 由于是并发的写数据,多个goroutine争抢资源,修改数据,最后count累加的结果并不是10000
func main() {
    var count = 0
    var wg sync.WaitGroup
    //十个协程数量
    n := 10
    wg.Add(n)
    for i := 0; i < n; i++ {
        go func() {
            //1万叠加
            for j := 0; j < 10000; j++ {
                count++
            }
            defer wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println(count)
}
  • 2 加上互斥锁后就能保证在写操作的时候,保证没有其他goroutine会篡改
func main() {
    var count = 0
    var wg sync.WaitGroup
    //十个协程数量
    n := 10
    m := sync.Mutex{}
    wg.Add(n)
    for i := 0; i < n; i++ {
        go func() {
            //1万叠加
            for j := 0; j < 10000; j++ {
                m.Lock()
                count++
                m.Unlock()
            }
            defer wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println(count)  10000
}

sync.RWMutex

  • 1 前提是明确知道当下的应用场景,是大量的读和少量写,就可以用RWMutex替代 Mutex

  • 2 包括的方法: Lock/Unlock --- 针对写操作,后面来的资源都会阻塞,知道执行Unlock()释放锁。

  • 3 RLock/RUnlock:针对读操作的方法,当锁被读操作获取的时候,RLock方法会直接返回。如果是被写操作获取的时候,RLock方法会一直阻塞,直到RUnlock释放锁。

  • 4 并发读

func main() {
    go reads(1)
    go reads(2)
    go reads(3)

    time.Sleep(time.Second * 3)
}
func reads(num int) {
    var m sync.RWMutex
    fmt.Println("reads start")
    m.RLock()
    fmt.Println(num, "reads")
    time.Sleep(time.Second * 2)
    m.RUnlock()
    fmt.Println(num, "reads end~!")
}
  • 5 并发写的场景, 写的时候其他协程阻塞
func main() {
    var m sync.RWMutex
    for i := 1; i <= 3; i++ {
        go writes(&m, i)
    }
    for j := 1; j <= 3; j++ {
        go reads(&m, j)
    }
    time.Sleep(time.Second * 2)
    fmt.Println("do work end~", count)
}

func reads(m *sync.RWMutex, num int) {
    fmt.Println("reads start")
    m.RLock()
    fmt.Println("read下标:", num, count)
    time.Sleep(1 * time.Millisecond)
    m.RUnlock()
}
func writes(m *sync.RWMutex, j int) {
    fmt.Println("write start")
    m.Lock()
    count++
    fmt.Println("write下标:", j, count)
    time.Sleep(1 * time.Millisecond)
    m.Unlock()
}
// 控制台输出结果
write start
write start
write下标: 1 1  并发写会阻塞
reads start
write start
reads start
reads start
read下标: 2 1   并发读可以直接返回
read下标: 3 1
read下标: 1 1
write下标: 2 2
write下标: 3 3
do work end~ 3

事务

  • 1 事务默认是系统自动提交的!
  • 2 事务的四大特性:原子性(不可拆分),一致性(结果一致),隔离性(独立操作),持久性(永久存储在磁盘)
  • 3 开始事务命令:start transaction 或者 begin

并发事务引发的问题

  • 1 脏读:一个事务读到另一个事务没有提交的数据
  • 2 不可重复读:一个事务先后读取同一条记录,但是2个结果不一致
  • 3 幻读:一个事务在查询该条记录时没有发现数据存在,但是在插入数据的时候,又发现已经存在,产生幻觉,也就是幻读。

事务的隔离级别就是解决事务并发的问题

  • 1 read uncommited(读未提交),所有并发问题都会产生。
  • 2 read commited (读已提交),会产生不可重复读和幻读。
  • 3 repeatable read(默认:可重复读),会产生幻读的问题。
  • 4 serializable (串行化),可以解决所有并发问题,但是性能是最慢的。

Mysql 结构

  • 4层结构


    1709379398248.jpg
  • 存储引擎是针对表的,不是库的。
  • 不同的引擎有不一样的应用场景。
  • 5.5版本之后的Mysql默认引擎是: InnoDB。
  • Memory引擎 : 数据是存放在内存中,速度快,使用这个引擎的时候该表只能作为临时表或者缓存来用!


    image.png

索引的数据结构

  • Innodb 引擎的索引是用B+Tree 结构:所有叶子节点存放数据,非叶子节点存索引,叶子节点形成双向链表。所有数据都存在一个空间:表空间---段---区---页---行,它们是层层包含的关系。
  • Innodb的3大特性:事务,外键,行锁

-
image.png
  • 一个页的空间大小是16KB
  • B+Tree相比于二叉树来说的优点:层级少,搜索速度快

索引分类

image.png
image.png
  • 聚集索引挂的是表里的这一行的行数据

  • 二级索引挂的是聚集索引的ID,也就是通过这个id去聚集索引下面检索数据,2个索引都是B+Tree结构

  • 下面这个查询语句先走二级索引,找到对应的键值再去聚集索引里找对应的数据!这个过程叫做回表查询!


    image.png
  • 指针比Key要多一个,一个指针是占6字节,而Key值要根据具体类型来计算,比如一个bigint占8字节。

Query 查询语句优化

  • 可以通过查询语句查看sql语句的执行具体情况,以便做性能优化。
show global status like `Com______`; // 7个横杠
image.png

Explain 检测sql语句的性能

  • 可以通过Explain 来查看Type字段下的索引是什么,如果是All就代表查询性能最差,可以对Sql进行优化。

  • 创建索引命令: create index idx_sn on 表名(字段名)

  • 最左匹配原则是针对联合索引来的,如果没有走最左匹配就会全表扫描,用不上索引性能就会差一点!如果查询多个字段,并且跳过了索引中的列,那索引会部分失效,前提是走了联合索引,也就是最左匹配走了。

  • 当出现比较范围查询时,最好加上 >= 可以规避索引失效问题!


    image.png
  • 尽量不要进行函数运算,否则索引会失效。

  • 查询的时候,如果DB里存的是字符串,查询语句没有加引号,也会导致索引失效,检索效率降低。


    image.png
  • 头部模糊匹配,索引会失效。


    1709555920234.jpg

索引提示性使用

  • use index (标识使用具体那个索引)
  • ignore index (忽略使用某索引)
  • force index (强制使用某索引)
Explain select * from user ignore index(索引名) where phone="xxxx"
  • 覆盖索引:就是回表查询的时候一次就查询到数据,不需要再去聚集索引检索数据
这种情况要优化的话,最好是建立联合索引走覆盖索引检索数据,不需要回表二次查询,性能是最优!
- select id,username,password from user where password ="xxxx"
  • 前缀索引:是用在查询某字段,因数据太长或者文本体积太大,而截取其中一小段建立索引,来提高检索速度的!
image.png

Limit优化

  • 可以采用子查询的方式进行优化(数据量特别大的时候)


    image.png

Count优化

  • 遇到数据量大的情况,count可以存redis,每次插入一条数据就累加,自己维护一个总数,当数据量大时去Mysql查询会比较慢
  • count查询的效率对比:
count(字段) < count(主键id) < count(1) < count(*) # count(*)是最快的,Innodb专门针对count(*)进行了优化,不需要累加计数

Update 优化

  • Innodb的行锁是针对索引加的,不是针对记录加的锁,并且索引不能失效,否则行锁会升级成表锁,也就是当insert操作的时候会锁住一整张表,其他操作提交不了。并发性能就会降低

按照锁的粒度划分

  • 全局锁:开启全局锁时,不能进行增删改操作,只能进行查询操作。

  • 表级锁:表锁又细分写和读锁,同样开启读锁是,其他DML操作阻塞,开启写锁时,其他的DML和DQL都不能操作。

  • 行级锁: 行锁锁的是索引,不是叶子节点的数据。

  • 当操作的字段没有索引,开启事务操作的时候行锁就会升级成表锁,其他操作会阻塞。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容