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 之后带的字段都可以走索引。
- 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层结构
- 存储引擎是针对表的,不是库的。
- 不同的引擎有不一样的应用场景。
- 5.5版本之后的Mysql默认引擎是: InnoDB。
-
Memory引擎 : 数据是存放在内存中,速度快,使用这个引擎的时候该表只能作为临时表或者缓存来用!
索引的数据结构
- Innodb 引擎的索引是用B+Tree 结构:所有叶子节点存放数据,非叶子节点存索引,叶子节点形成双向链表。所有数据都存在一个空间:表空间---段---区---页---行,它们是层层包含的关系。
- Innodb的3大特性:事务,外键,行锁
- 一个页的空间大小是16KB
- B+Tree相比于二叉树来说的优点:层级少,搜索速度快
索引分类
聚集索引挂的是表里的这一行的行数据
二级索引挂的是聚集索引的ID,也就是通过这个id去聚集索引下面检索数据,2个索引都是B+Tree结构
-
下面这个查询语句先走二级索引,找到对应的键值再去聚集索引里找对应的数据!这个过程叫做回表查询!
指针比Key要多一个,一个指针是占6字节,而Key值要根据具体类型来计算,比如一个bigint占8字节。
Query 查询语句优化
- 可以通过查询语句查看sql语句的执行具体情况,以便做性能优化。
show global status like `Com______`; // 7个横杠
Explain 检测sql语句的性能
可以通过Explain 来查看Type字段下的索引是什么,如果是All就代表查询性能最差,可以对Sql进行优化。
创建索引命令: create index idx_sn on 表名(字段名)
最左匹配原则是针对联合索引来的,如果没有走最左匹配就会全表扫描,用不上索引性能就会差一点!如果查询多个字段,并且跳过了索引中的列,那索引会部分失效,前提是走了联合索引,也就是最左匹配走了。
-
当出现比较范围查询时,最好加上 >= 可以规避索引失效问题!
尽量不要进行函数运算,否则索引会失效。
-
查询的时候,如果DB里存的是字符串,查询语句没有加引号,也会导致索引失效,检索效率降低。
-
头部模糊匹配,索引会失效。
索引提示性使用
- use index (标识使用具体那个索引)
- ignore index (忽略使用某索引)
- force index (强制使用某索引)
Explain select * from user ignore index(索引名) where phone="xxxx"
- 覆盖索引:就是回表查询的时候一次就查询到数据,不需要再去聚集索引检索数据
这种情况要优化的话,最好是建立联合索引走覆盖索引检索数据,不需要回表二次查询,性能是最优!
- select id,username,password from user where password ="xxxx"
- 前缀索引:是用在查询某字段,因数据太长或者文本体积太大,而截取其中一小段建立索引,来提高检索速度的!
Limit优化
-
可以采用子查询的方式进行优化(数据量特别大的时候)
Count优化
- 遇到数据量大的情况,count可以存redis,每次插入一条数据就累加,自己维护一个总数,当数据量大时去Mysql查询会比较慢
- count查询的效率对比:
count(字段) < count(主键id) < count(1) < count(*) # count(*)是最快的,Innodb专门针对count(*)进行了优化,不需要累加计数
Update 优化
- Innodb的行锁是针对索引加的,不是针对记录加的锁,并且索引不能失效,否则行锁会升级成表锁,也就是当insert操作的时候会锁住一整张表,其他操作提交不了。并发性能就会降低
按照锁的粒度划分
全局锁:开启全局锁时,不能进行增删改操作,只能进行查询操作。
表级锁:表锁又细分写和读锁,同样开启读锁是,其他DML操作阻塞,开启写锁时,其他的DML和DQL都不能操作。
行级锁: 行锁锁的是索引,不是叶子节点的数据。
当操作的字段没有索引,开启事务操作的时候行锁就会升级成表锁,其他操作会阻塞。