数据库基础&Gorm | 青训营笔记

58 阅读7分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记。

SQL基础

数据库:长期存储在计算机内、有组织的、可共享的数据的集合。

关系型数据库:采用关系模型来组织数据的数据库,简单来说,关系模型就是二维表格模型。

非关系型数据库:非关系型的、分布式系统的,且一般不确保遵照ACID标准的数据存储系统。

MYSQL约束——什么是约束?什么是唯一约束、主键约束、联合主键约束、非空约束?

  1. 约束主要完成对数据的校验,保证数据库数据的完整性;如果有相互依赖的数据,保证数据不会被删除。

  2. 主要有五种约束:

    • not null: 非空约束,指定某列不能为空。
    • unique: 唯一约束,指定某列和几列组合地数据不能重复。
    • primary key: 主键约束,指定某列地数据不能重复,唯一。
    • foreign key: 外键约束,指定子表种该列记录属于主表中的一条记录,参照另一条数据。
    • check: 检查 指定一个表达式,用于检验指定数据。

MYSQL怎么修改已经被外键约束的表?

  1. 可以先取消外键,修改完后再加回来,但是比较费事;

  2. mysql提供了一个方法,临时关闭外键约束,修改完成之后再将外键约束加回来。

    SET FOREIGN_KEY_CHECKS = 0;
    ....//do something
    SET FOREIGN_KEY_CHECKS = 1;

MYSQL like查询的弊端

在不使用索引的情况下,普通查询和like查询的耗时相当,like略长,这也是必然的,因为它要进行额外的算法。

在使用索引的情况下,普通查询的耗时基本上算是秒查,非常快,但是like查询较慢。原因是普通查询用到了索引,但是like语句并没有用到索引。

造成mysql中like查询效率低下的原因是:在有些情况下,like查询使用不到索引,会扫描全表。但是like语句有时候也会使用到索引,例如'dd_' 'dd%'这种还是可以用到索引的,因此要尽量避免使用以%或者_开头进行模糊查询,不然会造成索引失败。数据量大的时候,耗时,效率较低。

数据库事务

数据库事务是数据库运行中的逻辑工作单位,一条或者一组语句组成的一个单元,这个单元要么全部执行,要么全都不执行。

数据库事务的四大特性ACID:

  • 原子性: 要么全部执行,要么全都不执行。
  • 一致性: 事务必须使得数据库从一个一致性状态,到另一个一致性状态。
  • 隔离性: 指的是一个事务的执行,不能被其他的事务所干扰。
  • 持久性: 一个事务一旦提交了之后,对数据库的改变就是永久的。 数据库的事务隔离级别

什么是脏读、不可重复读、幻读。

(1) 脏读: 读到了未提交事务的数据。

image.png

事务A触发了回滚,那么事务B读到的数据就是过时的数据,这种现象就是脏读。

(2) 不可重复读: 事务A在事务B更新数据之前先读取了一条数据,后来事务B更新之后,事务A再次读取了这条数据,发现数据不匹配,这个现象称作"不可重复读"。

image.png

(3) 幻读: 在一个事务内,同一条查询语句在不同时间段执行,得到不同的结果集。

image.png

脏读和不可重复读都是属于多次读取数据 数据状态的不一致性。

事务隔离级别:

级别逐渐增强

(1) 读未提交 read uncommitted: 隔离级别最低、隔离度最弱。脏读、不可重复读、幻读三种现象都有可能发生。理论上的存在,实际项目中没有人用,但性能最高。

(2) 读已提交 read committed: 保证事务不出现中间状态,所有的数据都是已提交的,解决了脏读的问题,但是仍然存在不可重复读、幻读的可能。

(3) 可重复读 repeatable reads: mysql innoDB引擎的默认隔离级别,保证同一个事务多次读取数据的一致性,解决了脏读不可重复读的问题,但是仍然存在幻读的可能。

(4) 可串行化 serializable reads: 可串行化意味着读取数据的时候需要获取共享读锁,更新数据的时候需要获取排他写锁,如果sql使用where语句,还会获取区间锁。也就是事务A操作数据库的时候,事务B只能排队等待,因此性能是最低的。

Gorm

连接数据库

//连接数据库,并设置慢打印
	var err error
	dsn := "root:6J&fhT5P0gdi@tcp(localhost:3306)/gorm_demo?charset=utf8mb4&parseTime=True&loc=Local"
	newLogger := logger.New(
        log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
        logger.Config{
        SlowThreshold:             time.Second, // 慢 SQL 阈值
        LogLevel:                  logger.Info, // 日志级别
        IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
        Colorful:                  true,        // 彩色打印
        },
    )
	//全局模式
	db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
        Logger: newLogger,
    })
	if err != nil {
		panic(err)
	}

通过Gorm生成表结构

type Person struct {
	gorm.Model
	Name        string
	CreditCards []CreditCard
}
------------------------------
//通过自动迁移建立表
db.AutoMigrate(&Person{})

Gorm中对零值的处理,update和updates都有哪些坑?

Update用于更新单个的列,需要指定条件,不然会返回ErrMissingWhereClause错误。当使用Model方法,且该对象主键有值,该值会被用于构建条件。
//使用where条件
db.Model(&User{}).Where("name = ?", "hello").Update("name", "world");
//使用Model ID
db.Model(&user).Update("name", "张三");
//使用Model和where 查找指定id的user,同时名称为张三的记录,名称修改为李四。
db.Model(&user).Where("name = ?", "张三").Update("name", "李四");
Updates方法支持structmap[string]interface{}参数。
当使用struct进行更新的时候,gorm只会更新非零值的字段。
//struct参数 
db.Model(&user).Updates(User{Name: "王五", Age: 23});
//map[string]interface{}参数
//里面的参数应该是数据库所对应的(其实struct对应的大写的字段的也行,会自动转换)
db.Model(&user).Updates(map[string]interface{}{
    "name": "王五",
    "age": 23,
})

添加数据

	user := User{
		Name: "马云",
		Age:  56,
	}
	//1. create单个
	db.Create(&user)
	//2. create批量
	users := []User{
		{
			Name: "任一",
			Age:  25,
		},
		{
			Name: "任二",
			Age:  26,
		},
	}
	db.Create(&users)
	//3. CreateInBatches分批创建 可以指定每批的数量
	db.CreateInBatches(users, 2)
	//4.使用map[string]interface{}和[]map[string]interface{}{}创建记录
	//主键不会自动填充
	db.Model(&User{}).Create(map[string]interface{}{
		"name":  "任三",
		"email": "56789@qq.com",
	})

	db.Model(&User{}).Create([]map[string]interface{}{
		{
			"name": "任四",
		},
		{
			"name": "任五",
		},
	})

查询数据

//获取第一条记录
//SELECT * FROM users ORDER BY id LIMIT 1;
var user User
db.First(&user)
fmt.Println(user) //1
//获取最后一条记录
//SELECT * FROM users ORDER BY id DESC LIMIT 1;
var userLast User
db.Last(&userLast)
fmt.Println(userLast) //20
//获取一条记录 不指定排序
//SELECT * FROM `users` LIMIT 1
var random User
db.Take(&random)
fmt.Println(random)
//按照主键进行检索
var userId4 User
db.First(&userId4, 4)
fmt.Println(userId4)
//多个主键 获取数组
var usersId123 []User
db.Find(&usersId123, []int{1, 2, 3})
fmt.Println(usersId123)
//如果主键是字符串 例如uuid
//db.Find(&user, "id = ?", "nfeiwnfi-37r892bjfsd0-svsi")
var userbyname User
//获取匹配到的第一条记录
db.Where("name = ?", "1111").First(&userbyname)
fmt.Println(userbyname)
var usersbyname []User
//获取匹配到的全部记录
db.Where("name = ?", "1111").Find(&usersbyname)
fmt.Println(usersbyname)

var user User
//传入多个主键 27不存在 3 4存在
db.First(&user, 27, 3, 4)
fmt.Println(user)
//只打印了主键为3的那一条记录,说明程序会一个个的找,直到找到存在的那一条记录就会停止

//带条件的查询 面向对象的
//使用map进行查询
var usersList []User
db.Where(map[string]interface{}{"Name": "1111"}).Find(&usersList)
for _, item := range usersList {
    fmt.Println(item.ID)
}

Gorm如何实现删除数据的?

//删除
//一般开发中都会使用软删除
//gorm 只要model中包含gorm.Model或者包含gorm.DeleteAt字段,会自动获得软删除的能力
//软删除,不会真正的删除,只是DeleteAt会置为当前删除时间,并且不能通过普通的查询方法找到该记录,查询的时候会自动忽略
db.Where("Name = ?", "world").Delete(&User{})
//查询刚才删除的Name="world"记录
users := []User{}
db.Where("Name = ?", "world").Find(&users)
fmt.Println(users)
-------------------------------------------------------------
[1.949ms] [rows:0] SELECT * FROM `users` WHERE Name = 'world' AND `users`.`deleted_at` IS NULL //自动添加了delete_at字段为空                                     
[]    //空集 查询不到
------------------------------------------------------------
//使用Unscoped找到被软删除的记录
users := []User{}
db.Unscoped().Where("Name = ?", "world").Find(&users)
fmt.Println(users)