无事小神仙
技术瓶颈?如何解决MongoDB超大块数据问题?
最近项目在使用MongoDB作为图片和文档的存储数据库,为啥不直接存MySQL里,还要搭个MongoDB集群,麻不麻烦?让我们一起,一探究竟,继续学习解决MongoDB超大块数据问题,实现快速入门,丰富个人简历,提高面试level,给自己增加一点谈资,秒变面试小达人,BAT不是梦。一、MongoDB服务器管理1、添加服务器可以在任何时间添加mongos进程,只要确保,它们的 --configdb选项指定了正确的配置服务器副本集,并且客户端可以立即与其建立连接。2、修改分片中的服务器要修改一个分片的成员,需要直接连接到该分片的主节点,并重新配置副本集。集群配置会检测到变更并自动更新 config.shards。3、删除分片一般情况下,不应该从集群中删除分片,会给系统带来不必要的压力。删除分片时,要确保均衡器的打开状态。均衡器的作用是把要删除分片上的所有数据移动到其它分片,这个过程称为排空。可以通过 removeShard命令执行排空操作。二、均衡器可以通过 sh.setBalancerState(false)关闭均衡器。关闭均衡器不会将正在进行的过程停止,也就是说迁移过程不会立即停止。通过db.locks.find({"_id","balancer"})["state"]查看均衡器是否关闭。0表示均衡器已关闭。均衡过程会增加系统的负载,目标分片必须查询源分片的所有文档,并将文档插入目标分片的块中,然后源分片必须删除这些文档。数据迁移是很消耗性能的,此时可以在config.settings集合中为均衡过程指定一个时间窗口。将其指定在一个闲暇时间执行。如果设置了均衡窗口,应该对其进行监控,确保mongos能够在所分配的时间内保持集群的均衡。均衡器使用块的数量而不是数据的大小作为度量。移动一个块被称为迁移,这是MongoDB平衡数据的方式。可能会存在一个大块的分片称为许多小分片迁移的目标。三、修改块的大小一个块可以存放数百万个文档,块越大,迁移到另一个分片所花费的时间就越长,默认情况下,块的大小为64MB。但对于64MB的块,迁移时间太长了,为了加快迁移速度,可以减少块的大小。比如将块的大小改为32MB。db.settings.save({"_id","chunksize","value":32})已经存在的块不会发生改变,自动拆分仅会在插入或更新时发生,拆分操作是无法恢复的,如果增加了块的大小,那么已经存在的块只会通过插入或更新来增长,直到它们达到新的大小。块大小的取值范围在1MB到1024MB。这是一个集群范围的设置,会影响所有的集合和数据库。因此,如果一个集合需要较小的块,另一个集合需要较大的块,那么可能需要在这两个大小间取一个折中的值。如果MongoDB的迁移过于频繁或者使用的文档太大,则可能需要增加块的大小。四、超大块一个块的所有数据都位于某个特定的分片上。如果最终这个分片拥有的块比其它分片多,那么MongoDB会将一些块移动到其它分片上。当一个块大于 config.settings中所设置的最大块大小时,均衡器就不允许移动这个块了。这些不可拆分、不可移动的块被称为超大块。1、分发超大块要解决超大块引起的集群不均衡问题,就必须将超大块均匀地分配到各个分片中。2、分发超大块步骤:关闭均衡器 sh.setBalancerState(false);因为MongoDB不允许移动超过最大块大小的块,所以要暂时先增大块大小,使其超过现有的最大块块大小。记录下当时的块大小。db.settings.save({"_id","chunksize","value":maxInteger});使用moveChunk命令移动分片中的超大块;在源分片剩余的块上运行splitChunk命令,直到其块数量与目标分片块数量大致相同;将块大小设置为其最初值;开启均衡器3、避免出现超大块更改片键,使其拥有更细粒度的分片。通过db.currentOp()查看当前操作,``db.currentOp()```最常见的用途是查找慢操作。MongoDB Enterprise > db.currentOp()
{
"inprog" : [
{
"type" : "op",
"host" : "LAPTOP-P6QEH9UD:27017",
"desc" : "conn1",
"connectionId" : 1,
"client" : "127.0.0.1:50481",
"appName" : "MongoDB Shell",
"clientMetadata" : {
"application" : {
"name" : "MongoDB Shell"
},
"driver" : {
"name" : "MongoDB Internal Client",
"version" : "5.0.14"
},
"os" : {
"type" : "Windows",
"name" : "Microsoft Windows 10",
"architecture" : "x86_64",
"version" : "10.0 (build 19044)"
}
},
"active" : true,
"currentOpTime" : "2023-02-07T23:12:23.086+08:00",
"threaded" : true,
"opid" : 422,
"lsid" : {
"id" : UUID("f83e33d1-9966-44a4-87de-817de0d804a3"),
"uid" : BinData(0,"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=")
},
"secs_running" : NumberLong(0),
"microsecs_running" : NumberLong(182),
"op" : "command",
"ns" : "admin.$cmd.aggregate",
"command" : {
"aggregate" : 1,
"pipeline" : [
{
"$currentOp" : {
"allUsers" : true,
"idleConnections" : false,
"truncateOps" : false
}
},
{
"$match" : {
}
}
],
"cursor" : {
},
"lsid" : {
"id" : UUID("f83e33d1-9966-44a4-87de-817de0d804a3")
},
"$readPreference" : {
"mode" : "primaryPreferred"
},
"$db" : "admin"
},
"numYields" : 0,
"locks" : {
},
"waitingForLock" : false,
"lockStats" : {
},
"waitingForFlowControl" : false,
"flowControlStats" : {
}
},
{
"type" : "op",
"host" : "LAPTOP-P6QEH9UD:27017",
"desc" : "Checkpointer",
"active" : true,
"currentOpTime" : "2023-02-07T23:12:23.086+08:00",
"opid" : 3,
"op" : "none",
"ns" : "",
"command" : {
},
"numYields" : 0,
"locks" : {
},
"waitingForLock" : false,
"lockStats" : {
},
"waitingForFlowControl" : false,
"flowControlStats" : {
}
},
{
"type" : "op",
"host" : "LAPTOP-P6QEH9UD:27017",
"desc" : "JournalFlusher",
"active" : true,
"currentOpTime" : "2023-02-07T23:12:23.086+08:00",
"opid" : 419,
"op" : "none",
"ns" : "",
"command" : {
},
"numYields" : 0,
"locks" : {
},
"waitingForLock" : false,
"lockStats" : {
},
"waitingForFlowControl" : false,
"flowControlStats" : {
}
}
],
"ok" : 1
}4、输出内容详解:opid,操作的唯一标识,可以使用这个字段来终止操作;active,操作是否正在进行,如果为false,意味着此操作已经让出或者正在等待其它操作交出锁;secs_running,操作的持续时间,可以使用这个字段查询耗时过长的操作;op,操作类型,通常为query、insert、update、remove;desc,客户端的标识符,可以与日志中的消息相关联;locks,描述操作所涉及的锁类型;waitingForLock,当前操作是否处于阻塞中并等待获取锁;numYields,操作释放锁以允许其它操作进行的次数。一个操作只有在其它操作进入队列并等待获取它的锁时才会让出自己的锁,如果没有操作处于waitingForLock状态,则当前操作不会让出锁;lockStats.timeAcquiringMiros,操作为了获取锁所花费的时间;通过``db.currentOp()找到慢查询后,可以通过db.killOp(opid)```的方式将其终止。并不是所有操作都可以被终止,只有当操作让出时,才能终止,因此,更新、查找、删除操作都可以被终止,但持有或等待锁的操作不能被终止。如果MongoDB中的请求发生了堆积,那么这些写操作将堆积在操作系统的套接字缓冲区,当终止MongoDB正在运行的写操作时,MongoDB依旧会处理缓冲区的写操作。可以通过开启写入确认机制,保证每次写操作都要等前一个写操作完成后才能执行,而不是仅仅等到前一个写操作处于数据库服务器的缓冲区就开始下一次写入。五、系统分析器系统分析器可以提供大量关于耗时过长操作的信息,但系统分析器会严重的降低MongoDB的效率,因为每次写操作都会将其记录在system.profile中记录一下。每次读操作都必须等待system.profile写入完毕才行。开启分析器:MongoDB Enterprise > db.setProfilingLevel(2)
{ "was" : 0, "slowms" : 100, "sampleRate" : 1, "ok" : 1 }slowms决定了在日志中打印慢速操作的阈值。比如slowms设置为100,那么每个耗时超过100毫秒的操作都会被记录在日志中,即使分析器是关闭的。查询分析级别:MongoDB Enterprise > db.getProfilingLevel()
2重新启动MongoDB数据库会重置分析级别。六、一些常见的辅助命令通过Object.bsonsize函数获取其在磁盘中存储大小,单位是字节。> Object.bsonsize(db.worker.find())
65194使用mongotop统计哪些集合最繁忙。使用mongotop --locks统计每个数据库的锁信息。mongostat提供了整个服务器范围的信息。
无事小神仙
MongoDB数据库性能监控看这一篇就够了
最近项目在使用MongoDB作为图片和文档的存储数据库,为啥不直接存MySQL里,还要搭个MongoDB集群,麻不麻烦?让我们一起,一探究竟,继续学习MongoDB数据库性能监控,实现快速入门,丰富个人简历,提高面试level,给自己增加一点谈资,秒变面试小达人,BAT不是梦。一、MongoDB启动超慢1、启动日常卡住,根本不用为了截屏而快速操作,MongoDB启动真的超级慢2、启动MongoDB配置服务器,间歇性失败。3、查看MongoDB日志,分析“MongoDB启动慢”的原因。4、耗时“一小时”,MongoDB启动成功!二、原因分析在MongoDB关闭之前,有较大的索引建立的操作没有完成,MongoDB就直接shutdown了,等MongoDB再次启动的时候,MongoDB默认会将这个index重建好,重建期间处于startup状态。由于不清楚重建索引需要多久,因此可以通过重启mongod时加上–noIndexBuildRetry参数来跳过索引重建。等启动完成后,再创建这个索引。下面从几方面,监控一下MongoDB的性能问题。三、监控MongoDB内存使用情况常驻内存: 常驻内存是MongoDB在RAM中显式拥有的内存。如果查询一个集合数据,MongoDB会将其放入常驻内存中,MongoDB会获得其地址,这个地址不是RAM中数据的真实地址,而是一个虚拟地址。MongoDB可以将它传递给内核,内核会查找出数据的真实位置。如果内核需要从内存中清理缓存,MongoDB仍然可以通过该地址对其进行访问。MongoDB会向内核请求内存,然后内核会查看数据缓存,如果发现数据不存在,就会产生缺页错误并将数据复制到内存中,最后再返给MongoDB。虚拟内存: 操作系统提供的一种抽象,它对软件进程隐藏了物理存储的细节。每个进程都可以看到一个连续的内存地址空间。在Ops Manager中,MongoDB的虚拟内存是映射内存的两倍。映射内存: 包含MongoDB曾经访问过的所有数据。四、监控MongoDB磁盘空间当磁盘空间不足时,可以进行如下操作:可以添加一个分片;删除未使用的索引;可以执行压缩操作;关闭副本集成员,将其数据复制到更大的磁盘中挂载;用较大驱动器的成员替换副本集中的成员;五、MongoDB常用命令1、MongoDB获取系统信息db.hostInfo()2、MongoDB获取系统内存情况db.serverStatus().mem3、MongoDB获取连接数信息db.serverStatus().connections4、MongoDB获取全局锁信息db.serverStatus().globalLock5、MongoDB获取操作统计计数器db.serverStatus().opcounters6、MongoDB获取数据库状态信息db.stats()以上是MongoDB的重要指标,通过这些指标我们可以了解到MongoDB的运行状态,评估数据库的健康程度,并快速确定实际项目中遇到的性能瓶颈。比如项目中遇到的MongoSocketReadTimeoutException:com.mongodb.MongoSocketReadTimeoutException: Timeout while receiving message
at com.mongodb.connection.InternalStreamConnection.translateReadException(InternalStreamConnection.java:475)
at com.mongodb.connection.InternalStreamConnection.receiveMessage(InternalStreamConnection.java:226)
at com.mongodb.connection.UsageTrackingInternalConnection.receiveMessage(UsageTrackingInternalConnection.java:105)
at com.mongodb.connection.DefaultConnectionPool$PooledConnection.receiveMessage(DefaultConnectionPool.java:438)
at com.mongodb.connection.CommandProtocol.execute(CommandProtocol.java:112)
at com.mongodb.connection.DefaultServer$DefaultServerProtocolExecutor.execute(DefaultServer.java:168)
at com.mongodb.connection.DefaultServerConnection.executeProtocol(DefaultServerConnection.java:289)
at com.mongodb.connection.DefaultServerConnection.command(DefaultServerConnection.java:176)
at com.mongodb.operation.CommandOperationHelper.executeWrappedCommandProtocol(CommandOperationHelper.java:216)
at com.mongodb.operation.CommandOperationHelper.executeWrappedCommandProtocol(CommandOperationHelper.java:207)
at com.mongodb.operation.CommandOperationHelper.executeWrappedCommandProtocol(CommandOperationHelper.java:113)
at com.mongodb.operation.FindOperation$1.call(FindOperation.java:488)
at com.mongodb.operation.FindOperation$1.call(FindOperation.java:1)
at com.mongodb.operation.OperationHelper.withConnectionSource(OperationHelper.java:241)
at com.mongodb.operation.OperationHelper.withConnection(OperationHelper.java:214)
at com.mongodb.operation.FindOperation.execute(FindOperation.java:483)
at com.mongodb.operation.FindOperation.execute(FindOperation.java:1)
at com.mongodb.Mongo.execute(Mongo.java:818)六、MongoDB持久性1、复制延迟复制延迟是指从节点无法跟上主节点的速度。从节点一个操作的时间减去主节点此操作的时间,就是复制延迟。延迟应该尽可能的接近0,并且通常是毫秒级的。2、备份备份操作通常会将所有数据读入内存,因此,备份操作通常应该在副本集从节点而不是主节点进行,如果是单机MongoDB,则应该在空间时间进行备份,比如深夜凌晨。3、持久性持久性是数据库必备的一种特性,想象一下,如果数据库不具备持久性,如果数据库重启,数据全部丢失,太可怕了,不敢想。为了在服务器发生故障时提供持久性,MongoDB使用预写式日志机制,英文简称 WAL。WAL是数据库系统中一种常见的持久性技术。在数据存入数据库之前,将这些更改操作写到磁盘上。从MongoDB4.0开始,执行写操作时,MongoDB会使用与oplog相同的格式创建日志。oplog语句具有幂等性,不管执行多少次,结果都是一样的。MongoDB还维护了日志和数据库数据文件的内存视图。默认情况,每50毫秒会将日志条目刷新到磁盘上,每60秒会将数据库文件刷新到磁盘上。刷新数据的时间60秒间隔被称为检查点。日志用于将上一个检查点之后的数据提供持久性。MongoDB的持久性就是在发生故障时,重启之后,将日志中的语句重新执行一遍,以保证在关闭前丢失的数据重新刷新到MongoDB中。MongoDB会在data目录下创建一个journal的子目录,WiredTiger日志文件的名称为WiredTigerLog.<sequence>。sequence是一个从0 000 000 001开始的数字。MongoDB会对写入的日志进行压缩,日志文件限制的最大大小为100MB。如果大于100MB,MongoDB就会自动创建一个新的日志文件,由于日志文件只需在上次检查点之后恢复数据,因此在新的检查点写入完成时,旧的日志文件就会被删除。
无事小神仙
自从学习了MongoDB高可用,慢慢的喜欢上了它,之前确实冷落了
最近项目在使用MongoDB作为图片和文档的存储数据库,为啥不直接存MySQL里,还要搭个MongoDB集群,麻不麻烦?让我们一起,一探究竟,继续学习MongoDB高可用和片键策略,实现快速入门,丰富个人简历,提高面试level,给自己增加一点谈资,秒变面试小达人,BAT不是梦。一、复制在MongoDB中,创建副本集后就可以使用复制功能了,副本集是一组服务器,其中一个用于处理写操作的主节点primary,还有多个用于保存主节点数据副本的从节点secondary。如果主节点崩溃了,则从节点会选取出一个新的主节点。如果使用复制功能时有一台服务器停止运行了,那么仍然可以从副本集中的其它服务器访问数据。如果服务器上的数据已损坏或无法访问,则可以从副本集中的其它成员中创建一个新的数据副本。副本集中的每个成员都必须能够连接到其它成员,如果收到有关成员无法访问到其它成员,则可能需要更改网络配置以允许它们之间的连接。二、如何进行选举当一个从节点无法与主节点连通时,它就会联系并请求其它的副本集成员将自己选举为主节点。其它成员会做几项健全性检查:它们能否连接到主节点,而这个主节点是发起选举的节点无法连接到的?这个发起选举的从节点是否有最新数据?有没有其它更高优先级的成员可以被选举为主节点?MongoDB在3.2版本中引入了第1版复制协议。这是一个类PAFT的协议,并且包含了一些特定于MongoDB的副本集概念,比如仲裁节点、优先级、非选举成员、写入关注点等。还提出了很多新概念,比如更短的故障转移时间,大大减少了检测主节点失效的时间,它还通过使用term ID来防止重复投票。RAFT是一种共识算法,它被分解成了相对独立的子问题。共识是指多台服务器或进程在一些值上达成一致的过程。RAFT确保了一致性,使得同一序列的命令产生相同序列的结果,并在所部署的各个成员中达到相同序列的状态。副本集成员相互间每隔两秒发送一次心跳。如果某个成员在10秒内没有反馈心跳,则其它成员会将不良成员标记为无法访问。选举算法将尽最大努力尝试让具有最高优先权的从节点发起选举。成员优先权会影响选举的时机和结果。优先级高的从节点要比优先级低的从节点更快发起选举,而且也更有可能成为主节点。然而,低优先级的从节点也是有可能被短暂的选举为主节点的,副本集成员会继续发起选举直到可用的最高优先级成员被选举为主节点。被选举为主节点的从节点必须拥有最新的复制数据。三、优先级优先级用于表示一个成员称为主节点的优先程度,取值范围是0 ~ 100。数值越大,优先级越高。默认为1,如果将priority设置为0,表示此节点永远无法成为主节点,这样的成员还有一个名字~被动成员。四、选举仲裁者大多数小型项目,MongoDB只有两个副本集,为了参与选举,MongoDB支持一种特殊类型的成员,称为仲裁者,其唯一作用就是参与仲裁。仲裁者不参与存储数据,也不会为程序提供服务,它只是为了帮助只有两个副本集的集群选举主节点(为了满足大多数),需要注意的是,只能有一个仲裁者。仲裁者的缺点:假设有一个主节点,两个从节点,一个仲裁者。如果一个从节点停止运行了,那么就需要一个新的从节点,并且将主节点的数据复制到新的从节点,复制数据会父服务器造成很大的压力,降低程序运行速度。所以,尽可能使用奇数的从节点,而不是使用仲裁者。五、同步MongoDB通过保存操作日志oplog使多台服务器间保持相同的数据,oplog中保存着主节点执行的每一次写操作。oplog存在于主节点local数据库中的一个固定集合中,从节点通过查询此集合以获取需要复制的操作。每个从节点同样维护着自己的oplog,用来记录它从主节点复制的每个操作。这使得每个成员都可以被用作其他成员的同步源。如果应用某个操作失败,则从节点会停止从当前数据源复制数据。如果一个从节点由于某种原因停止工作了,它重新启动后,会从oplog中的最后一个操作开始同步。由于这些操作是先应用到数据上然后再写入oplog,因此从节点可能会重复已经应用到数据上的操作。MongoDB在设计时考虑了这点,oplog中的操作执行一次和多次,效果都是一样的,oplog中的每个操作都是幂等的。六、处理过时数据如果某个从节点的数据远远落后于同步源当前的操作,那么这个从节点就是过时的。过时的从节点无法赶上同步源,如果继续同步,从节点就需要跳过一些操作。此时,需要从其它节点进行复制,看看其它成员是否有更长的oplog以继续同步。如果都没有,该节点当前的复制操作将停止,需要进行完全同步或从最近的备份中恢复。为了避免出现不同步的节点,让主节点拥有比较大的oplog以保存足够多的操作日志。七、哈希片键为了尽可能快地加载数据,哈希片键是最好的选择。哈希片键可以使任何字段随机分发。如果打算在大量查询中使用升序键,但又想在写操作时随机分发,哈希片键是不错的选择,不过需要注意的是,哈希片键无法执行指定目标的范围查询。创建哈希片键:db.users.createIndex({"name":"hashed"})有一点需要注意,哈希片键的字段,不能是数组。Error: hashed indexes do not currently support array values.八、多热点单独的mongod服务器在执行升序写操作时效率最高,这与分片相冲突,当写操作分发在集群中时分片效率最高。每个分片上都有几个热点,便于写操作在集群中均匀分发。可以使用复合片键实现均匀分发,复合片键的第一个值可以是一个基数较小的值,片键的第二部分是一个升序值,这意味着在块的内部,值总是在增加的。九、分片规则1、分片的限制比如上图的异常,片键不能是数组,大多数特殊类型的索引不能用作片键。特别是,不能在地理空间索引上进行分片。2、片键的基数片键与索引类似,在基数高的字段上进行分片,性能会更好。如果有一个status键,只有“正常”、"异常"、“错误”几个值,MongoDB是无法将数据拆分成3个以上的块(因为目前只有三个值),如果想将一个取值较小的键作为片键,那么可以将其与另一个拥有多值的键组成复合片键,比如createTime字段。这样复合片键就拥有了较高的基数。十、控制数据分发1、自动分片MongoDB将集合均匀分发在集群中的每个分片上,如果存储的是同构数据,那么这种方式非常高效。如果有一个日志集合,价值不是很大,你可能不希望它存储在性能最好的服务器上,性能最好的服务器一般会存储重要的实时数据,而不允许其它集合使用它。可以通过sh.addShardToZone("shard0","hign")、sh.addShardToZone("shard1","low")、sh.addShardToZone("shard2","low")实现它。可以将不同的集合分配给不同的分片,比如,对及其重要的实时集合执行:sh.updateZoneKeyRange("super.important",{"<shardKey>":MinKey},...{"<shardKey>":MaxKey},"high")这条命令指的是:对于这个集合super.important,将片键从负无穷到正无穷的数据保存在标记为“high”的分片上。这不会影响其它集合的均匀分发。同样可以通过low,将不重要的日志集合存放在性能较差的服务器上。sh.updateZoneKeyRange("super.logs",{"<shardKey>":MinKey},...{"<shardKey>":MaxKey},"low")此时,日志集合就会均匀的分发到shard1和shard2上。同样,可以通过removeShardFromZone()从区域中删除分片。sh.removeShardFromZone("super.logs",{"<shardKey>":MinKey},...{"<shardKey>":MaxKey})2、手动分发可以通过关闭均衡器 sh.stopBalancer()启动手动分发。如果当前正在进行迁移,则此设置在迁移完成之前不会生效。一旦正在运行的迁移完成,均衡器就会停止移动数据。除非遇到特殊情况,否则,MongoDB应该使用自动分片,而不是手动分片。
无事小神仙
MongoDB 4.0支持事务了,还有多少人想用MySQL呢?
最近项目在使用MongoDB作为图片和文档的存储数据库,为啥不直接存MySQL里,还要搭个MongoDB集群,麻不麻烦?让我们一起,一探究竟,继续学习MongoDB的事务、连接池以及聚合框架,实现快速入门,丰富个人简历,提高面试level,给自己增加一点谈资,秒变面试小达人,BAT不是梦。一、MongoDB 不支持事务?一些第三方文章将 MongoDB 描述成 BASE 数据库。BASE 是指“基本可用、软状态、最终一致”。但这不是真的,从来都不是!MongoDB 从来都不是“最终一致”的。对主文档的读写是强一致性的,对单个文档的更新始终是原子的。软状态是指需要持续不断的更新数据,否则数据就会过期,但 MongoDB 并非如此。最后,如果太多的节点不可用,无法达成仲裁,MongoDB 将进入只读状态(降低可用性)。这是有意这么设计的,因为这样可以确保在出现问题时保持一致性。MongoDB 是一个 ACID 数据库。它支持原子性、一致性、隔离性和持久性。对单个文档的更新始终是原子的,从 4.0 版本开始,MongoDB 也支持跨多个文档和集合的事务。从 4.2 开始,甚至支持分片集群的跨分片事务。虽然 MongoDB 支持事务,但在使用它时仍然要谨慎。事务是以性能为代价的,而且由于 MongoDB 支持丰富的分层文档,如果你的模式设计正确,就没有必要经常跨多个文档更新数据。二、什么是事务?事务是数据库中处理的逻辑单元,包括一个或多个数据库操作,既可以是读操作,也可以是写操作,MongoDB支持跨个多操作、集合、数据库、文档和分片的ACID事务。事务的关键:它要么都成功,要么都失败。三、ACID的定义ACID是一个事务所需要具备的一组属性集合。ACID是原子性atomicity、一致性consistency、隔离性isolation、持久性durability的缩写。ACID事务可以确保数据和数据库状态的有效性,即使在出现断电或其它错误的情况下也是如此。原子性确保了事务中的所有操作要么都被执行、要么都不被执行。一致性确保可如果事务成功,那么数据库将从一个一致性状态转移到下一个一致性状态。隔离性是允许多个事务同时在数据库中运行的属性。它保证了一个事务不会查看到任何其它事务的部分结果,这意味着多个事务并行运行于依次运行每个事务所获得的结果都相同。持久性确保了在提交事务时,即使系统发生故障,所有数据也都会保持持久化。当数据库满足所有这些属性并且只有成功的事务才会被处理时,它就被称为符合ACID的数据库。如果在事务完成之前发生故障,ACID确保不会更改任何数据。MongoDB是一个分布式数据库,它支持跨副本集和跨分片的ACID事务。网络层增加了额外的复杂性。四、如何使用事务MongoDB提供了两种API来使用事务。第一种与关系型数据库类似(如start_transaction和commit_transaction),称为核心API;第二种称为回调API,一般推荐使用这种;核心API不会为大多数错误提供重试逻辑,它要求开发人员为操作、事务提交函数以及所需的任何重试和错误逻辑手动编写代码。与核心API不同,回调API提供了一个简单的函数,该函数封装了大量的功能,包括启动与指定逻辑会话关联的事务、执行作为回调函数提供的函数以及提交事务。回调API还提供了处理提交错误的重试逻辑。在MongoDB4.2中添加回调API是为了简化使用事务的应用程序开发,也便于添加处理事务错误的应用程序重试逻辑。核心API和回调API的比较核心API回调API需要显示调用才能启动和提交事务启动事务、执行指定操作,然后提交(可在发生错误前终止)不包含TransientTransactionError和UnknowTransactionCommitResult的错误处理逻辑,而是提供了为这些错误进行自定义处理的灵活性自动为TransientTransactionError和UnknowTransactionCommitResult提供错误处理逻辑要求为特定事务将显式的逻辑会话传递给API要求为特定事务将显式的逻辑会话传递给API五、重要参数简介在MongoDB事务中有两种限制,第一种是时间,控制事务的运行时间、事务等待获取锁的时间以及所有事务运行的最长时间;第二种是MongoDB的oplog条目和单个条目大大小限制;1、时间限制事务的默认最大运行时间是1分钟。可以通过修改transactionLifetimeLimitSeconds的限制来增加。对于分片集群,必须在所有分片副本集成员上设置该参数。超过此时间后,事务将被视为已过期,并由定期运行的清理进程终止。清理进程每60秒或transactionLifetimeLimitSeconds/2运行一次,以较小的值为准。要显式设置事务的时间限制,建议在提交事务时指定maxTimeMS参数。如果maxTimeMS没有设置,那么将使用transactionLifetimeLimitSeconds;如果设置了maxTimeMS,但这个值超过了transactionLifetimeLimitSeconds,那么还是会使用transactionLifetimeLimitSeconds。事务等待获取其操作所需锁的默认最大时间是 5 毫秒。可以通过修改maxTransactionLockRequestTimeoutMillis参数来控制。如果事务在此期间无法获取锁,则该事务会被终止。maxTransactionLockRequestTimeoutMillis可以被设置为0、-1或大于0的数字。设置为0,表示如果事务无法立即获得所需的所有锁,则该事务会被终止;设置为-1,将使用由maxTimeMS参数所指定的超时时间;设置为大于0的其它数字,将等待时间配置为该时间,尝试获取锁的等待时间也是该时间,单位秒;2、oplog大小限制MongoDB会创建出与事务中写操作数量相同的oplog数目。但是,每个oplog条目必须在16MB的BSON文档大小限制之内。六、连接池 = 数据库连接的缓存在最开始接触MongoDB的时候,是通过 MongoDatabase database = new MongoClient("localhost", 27017).getDatabase("test");的方式连接MongoDB。它会为每个请求创建一个新的连接,然后销毁,一般数据库的连接都是TCP连接,TCP是长连接,如果不断开,就会一直连着。众所周知,新建一个数据库连接的代价是很大的,复用现有连接才是首选,连接池就是干这个的。因此当需要新的连接时,就可以复用连接池中缓存的连接了。如果使用得当,连接池可以最大程度的降低数据库的新连接数量、创建频率。可以通过Mongo.get方法获得DB对象,表示MongoDB数据库的一个连接。默认情况下,当执行完数据库的查询操作后,连接将自动回到连接池中,通过api中的finally方法,将连接归还给连接池,不需要手动调用。1、MongoDB查询数据五步走MongoDB Client需要找到可用的MongoDB;Server MongoDB Client需要和 MongoDB Server建立 Connection;应用程序处理线程从 Connection Pool中获取 Connection;数据传输(获取连接后,进行 Socket 通信,获取数据);断开 Collection;2、MongoDB连接池的参数配置#线程池允许的最大连接数
connectionsPerHost: 40
#线程池中连接的最大空闲时间
threadsAllowedToBlockForConnectionMultiplier: 20
#1、MongoDB Client需要找到可用的MongoDB Server所需要的等待时间
serverSelectionTimeout: 40000
#2、MongoDB Client需要和MongoDB Server建立(new)Connection
connectTimeout: 60000
#3、应用程序处理线程从Connection Pool中获取Connection
maxWaitTime: 120000
#自动重连
autoConnectRetry: true
#socket是否保活
socketKeepAlive: true
#4、数据传输(获取连接后,进行Socket通信,获取数据)
socketTimeout: 30000
slaveOk: true
dbName: ngo
#是否进行权限验证
auth: false
#用户名
username: ngo
#密码
password: 12345678七、聚合框架聚合框架是MongoDB中的一组分析工具,可以对一个或多个集合中的文档进行分析。聚合框架基于管道的概念,使用聚合管道可以从MongoDB集合获取输入,并将该集合中的文档传递到一个或多个阶段,每个阶段对输入执行不同的操作。每个阶段都将之前阶段输出的内容作为输入。所有阶段的输入和输出都是文档,可以称为文档流。每个阶段都会提供一组按钮或可调参数,可以通过控制它们来设置该阶段的参数,以执行各种任务。这些可调参数通常采用运算符的形式,可以使用这些运算符来修改字段、执行算术运算、调整文档形状、执行各种累加任务或其它各种操作。常见的聚合管道包括匹配match、投射project、排序sort、跳过skip、限制limit。八、MongoDB文档格式设计文档中表示数据的方式,在进行文档格式设计时,首先需要了解查询和数据访问的方式。1、限制条件比如最大文档大小为16MB。2、查询和写入的访问模式通过了解查询的运行时间和频率,识别出最常见的查询,一旦确定了这些查询,就应该尽量减少查询的数量,并在文档设计中确保一起查询的数据存储在同一个文档中。这些查询中未使用的数据应该存放在不同的集合中。需要考虑是否可以将动态数据(读/写)和静态数据(读)分离开。在进行文档格式设计时,提高最常见查询的优先级会获得最佳的性能。3、关系类型根据业务逻辑、文档之间的关系来考虑哪些数据是相关的,确定使用嵌入还是引用。需要弄清楚如何在不执行其它查询的情况下引用文档,以及当关系发生变化时需要更新几个文档。还要考虑数据结构是否易于查询。4、范式化与反范式化范式化是指数据分散在多个集合中,在集合之间进行数据的引用;反范式化会将所有数据嵌入单个文档中;如何选择范式化与反范式化,范式化的写入速度更快,而反范式化的读取速度更快,因此需要根据应用程序的实际需求进行权衡。5、内嵌数据和引用数据对比更适合内嵌数据更适合引用数据较小子文档较大子文档数据不经常变更数据经常变更数据最终一致即可必须保证强一致性数据通常需要二次查询才能获得数据通常不包含在结果中快速读取快速写入6、优化数据操作优化读操作通常包括正确的索引和单个文档中返回尽可能多的数据; 优化写操作通常包括减少索引数量、尽可能的提高更新效率;通过删除旧数据进行优化:第一种方式是通过固定集合实现;第二种是使用TTL集合。TTL集合可以更精确的控制删除文档的时间,但在写入量过大的集合中操作速度不够快,通过遍历TTL索引来删除文档。第三种方式是分库分表。每个月的文档单独使用一个集合。这种方式实现起来更加复杂,因为需要使用动态集合或数据库名称,可能需要查询多个数据库。九、小结MongoDB从4.0开始支持事务了,MongoDB 是一个 ACID 数据库。它支持原子性、一致性、隔离性和持久性。虽然 MongoDB 支持事务,但在使用它时仍然要谨慎。事务是以性能为代价的;了解MongoDB了如何使用事务以及它的参数配置方案,达到即插即用的效果;对MongoDB查询数据的过程,有了更深层次的理解;领悟了MongoDB连接池的意义;深刻理解了MongoDB文档格式设计思想;总结了MongoDB读写的优化方式;
无事小神仙
一次线上事故,我顿悟了MongoDB的精髓
最近项目在使用MongoDB作为图片和文档的存储数据库,为啥不直接存MySQL里,还要搭个MongoDB集群,麻不麻烦?让我们一起,一探究竟,继续学习MongoDB分片的理论与实践,实现快速入门,丰富个人简历,提高面试level,给自己增加一点谈资,秒变面试小达人,BAT不是梦。三分钟你将学会:一次MongoDB线上事故的快速解决什么是MongoDB分片?MongoDB如何分片?何时分片?搭建MongoDB分片服务器MongoDB如何追踪分片集群数据?MongoDB拒绝连接?显然是MongoDB服务又挂了。连接MongoDB服务器,一探究竟。通过ps -aef|grep mongo查看mongo服务是否还在?不出所料,都不在了。大概率是因为磁盘满了。df -TH查看磁盘空间。磁盘100%如何解决?cd到log目录下,通过rm -rf *删除所有日志,再重启MongoDB。mongodb启动异常:about to fork child process, waiting until server is ready for connection由于MongoDB是集群部署的,启动时,会进行数据同步,可能会比较耗时,性子急的我,怎么能忍,直接Ctrl C,强制停止,然后再重新启动。通过ps -aef|grep mongo查看一下进程,两个一样的进程赫然在列。通过ps -aef|grep mongo | grep -v grep | awk '{print $2}' | xargs kill -9强制停止所有mongo进程。将data目录下的 mongod.lock 和 diagnostic.data文件删掉,再重启MongoDB,启动脚本mongos_start.sh(mongod --config data/mongodb.conf),完美解决。MongoDB服务器的部署目录中都是什么含义呢?它们之间又有什么关系呢?下面简单介绍一下MongoDB的分片。一、什么是MongoDB分片?分片是指跨机器拆分数据的过程,也可以叫做分区。MongoDB支持手动分区,使用这种方法,应用程序会维护到多个不同数据库服务器端的连接,每个服务器端都是完全独立的。应用程序不仅管理不同服务器上不同数据的存储,还管理在适当的服务器上查询数据。但当从集群中添加或删除节点,或者面对数据分布或负载模式的变化时,难以维护。MongoDB支持自动分片,这种方式试图将数据库架构从应用程序中抽离出来,并简化系统管理。MongoDB自动均衡分片上的数据,使节点的添加和删除变得更容易。MongoDB的分片机制允许你创建一个由许多分片组成的集群,并将集合中的数据分散在集群中,在每个分片上放置数据的一个子集。这允许应用程序超出单机服务器或副本集的资源限制。分片组成的集群对应用程序来说就像一台单机服务器,分片前运行一个或多个称为mongos的路由进程,mongos维护着一个“目录”,指明了每个分片包含哪些数据。应用程序可以正常连接到此路由服务器并发出请求。路由服务器知道哪些数据在哪个分片上,可以将请求转发到适当的分片。如果有对请求的响应,理由服务器会收集它们,并将它们合并,然后再返回给应用程序,对应用程序而言,它只知道自己连接到了一个单独的mongod。二、MongoDB如何分片?在单台机器上快速建立一个集群。首先,使用--nodb和--norc选项启动mongo shell:mongo --nodb --norc。使用ShardingTest类创建集群。运行如下代码:st = ShardingTest({
name:"one-min-shards",
chunkSize:1,
shards:2,
rs:{
nodes:3,
oplogSize:10
},
other:{
enableBalancer:true
}
});name:分片集群的标签;shards:制定了集群由两个分片组成;rs:将每个分片定义为一组3个节点的副本集;enableBalancer:在集群启动后启用均衡器;ShardingTest是为了支持服务器端测试套件设计的,它在保持尽可能低的资源占用以及建立体系结构相对复杂的分片集群方面,提供了很多便利。当运行ShardingTest后,它会创建一个包含两个分片的集群,每个分片都是一个副本集。同时会对副本集进行配置,并使用必要的选项启动每个节点以建立复制协议。它会启动一个mongos来管理跨分片的请求,这样客户端就可以像与一个独立的mongod通信一样与集群进行交互。最后,它会为用于维护理由表信心的配置服务器启动一个额外的副本集,以确保查询被定向到正确的分片。分片的主要使用场景是拆分数据集以解决硬件和成本的限制,或为应用程序提供更好的性能。当ShardingTest完成集群设置后,将启动并运行10个进程,你可以连接到这些进程:两个副本集(各有3个节点)、一个配置服务器副本集(3个节点),以及一个mongos。默认情况下,这些进程会从20000端口开始。mongos会运行在20009端口上。三、何时分片?通常情况下,分片用于:增加可用RAM;增加可用磁盘空间;减少服务器的负载;处理单个MongoDB无法承受的吞吐量;四、搭建MongoDB分片服务器1、配置服务器 config进程配置服务器是集群的大脑,保存着关于每个服务器包含哪些数据的所有元数据,因此必须首先创建配置服务器。配置服务器非常重要,运行时必须启动日志功能,并确保它的数据存储在非临时性驱动器上。配置服务器必须在任何一个mongos进程之前通过mongod -f config.conf启动,因为mongos需要从配置服务器中提取配置信息。当对配置服务器进行写入时,MongoDB会使用“majority” 的 writeConcern级别; 当对配置服务器进行读取时,MongoDB会使用“majority” 的 readConcern级别;这确保了分片集群元数据在不发生回滚的情况下才会被提交到配置服务器副本集。它还确保了只有那些不受配置服务器故障影响的元数据才能被读取。这可以确保所有mongos路由节点对分片集群中的数据组织方式具有一致性。在服务器资源方面,配置服务器应该具有充分的网络和CPU资源,配置服务器只保存了集群中数据的目录,因此只需要很少的硬盘存储资源。由于配置服务器的重要性,在进行任何集群维护前,都应该先对配置服务器的数据进行备份。2、mongos进程mongos 是路由服务器,供应用程序连接使用。通过mongod -f config.conf启动路由服务器,mongos进程需要知道配置服务器的地址,因此需要在config.conf中配置 configdb=configReplSet/配置服务器的三个地址,通过配置logpath,保存MongoDB的日志。应该启动一定数量的mongos进程,并尽可能将其放在靠近所有分片的位置,这样可以提高查询性能。3、将副本集转换为分片在依次启动配置服务器、路由服务器后,可以添加分片了,如果之前已经存在副本集,那么这个副本集就会成为第一个分片。从MongoDB 3.4 开始,对于分片集群,分片的mongod实例必须配置 --shardsvr 选项,也就是在config.conf中添加shardsvr=true,将副本集转换为分片的过程中,需要对副本集的每个成员都重复以上动作。将副本集作为分片添加到集群后,就可以将应用程序的连接从副本集改为mongos路由服务器了,并通过设置防火墙,切断应用程序与分片的直接连接。4、数据分片(1)如何数据分片假如有一个test数据库,并在name键上对worker集合进行分片。先对数据库进行分片,> sh.enableSharding("test");再对集合进行分片,sh.shardCollection("test.worker",{"name":1});如果worker集合已经存在,则必须在name字段上有索引,否则,shardCollection会返回错误。如果分片的集合不存在,mongos会自动在name片键上创建索引。shardCollection命令会将集合拆分成多个数据块,MongoDB会在集群中的分片间均匀的分散集合中的数据。五、MongoDB如何追踪集群数据?1、数据块因为MongoDB的数据量巨大,MongoDB一般会将文档以数据块的形式进行分组,这些数据块是片键指定范围内的文档,MongoDB一般会用一个较小的表来维护数据块与分片之间的映射关系。需要注意:块与块之间不能重叠;一个块中的文档数量过大时,会自动拆分成两个文档;一个文档总是属于且仅属于一个块;2、块范围新分片的集合中只有一个块,块的边界是负无穷到正无穷;随着块的增长,MongoDB会自动将其拆分成两块,范围从负无穷到value,value到正无穷。范围较小的块包含比value小的值,范围较大的块包含value和比value大的值;因此,mongos可以很容易的找到文档在哪个块。3、拆分块各个分片的主节点mongod进程会跟踪它们当前的块,一旦达到某个阈值,就会检查该块是否需要拆分,如果需要拆分,mongod就会从配置服务器请求全局块大小配置值,然后执行块拆分并更新配置服务器上的元数据。配置服务器会创建新的块文档,并修改旧块的范围。当客户端写入一个块时,mongod会检查该块的拆分阈值。如果已经达到了拆分阈值,mongod就会向均衡器发送一个请求,将最顶部的块进行迁移,否则该块会留在此分片上。因为具有相同片键的两个文档一定会处于相同的块中,所以只能在片键值不同的文档之间进行拆分。下面文档如果以readTime分片,是可以的。但是,如果我读书读的比较快,所有书籍在一个月的时间里都读完了,readTime就会是一样的了,那就无法分片了。因此拥有不同的片键值在分片时,显得尤其重要。{"name":"哪吒编程","book":"Java核心技术","readTime":"October"}
{"name":"哪吒编程","book":"Java编程思想","readTime":"October"}
{"name":"哪吒编程","book":"深入理解Java虚拟机","readTime":"October"}
{"name":"哪吒编程","book":"effective java","readTime":"November"}
{"name":"哪吒编程","book":"重构 改善既有代码的设计","readTime":"November"}
{"name":"哪吒编程","book":"高性能MySQL","readTime":"December"}
{"name":"哪吒编程","book":"Spring技术内幕","readTime":"December"}
{"name":"哪吒编程","book":"重学Java设计模式","readTime":"December"}
{"name":"哪吒编程","book":"深入理解高并发编程","readTime":"January"}
{"name":"哪吒编程","book":"Redis设计与实现","readTime":"January"}分片的前提条件是所有的配置服务器必须启动并可以访问。如果mongod不断接到对一个块的写请求,则它会持续尝试拆分该块并失败,而这些拆分尝试会拖慢mongod。mongod反复尝试分片却无法成功分片的过程被称为拆分风暴。六、均衡器均衡器负责数据的迁移。均衡器会定期检查分片之间是否存在不均衡,如果存在,就会对块进行迁移。在MongoDB 3.4 以上的版本上,均衡器位于配置服务器副本集的主节点成员上。均衡器是配置服务器副本集主节点上的后台进程,它会监视每个分片上的块数量。只有当一个分片上的块数量达到特定迁移阈值时,均衡器才会被激活。
无事小神仙
既然有MySQL了,为什么还要有MongoDB?
最近项目在使用MongoDB作为图片和文档的存储数据库,为啥不直接存MySQL里,还要搭个MongoDB集群,麻不麻烦?让我们一起,一探究竟,了解一下MongoDB的特点和基本用法,实现快速入门,丰富个人简历,提高面试level,给自己增加一点谈资,秒变面试小达人,BAT不是梦。三分钟你将学会:MongoDB主要特征MongoDB优缺点,扬长避短何时选择MongoDB?为啥要用它?MongoDB与MySQL关键字对比下载与安装过程中一些常见的坑Java整合MongoDB,实现农民工增删改查一、基本概念走起MongoDB是一款开源、跨平台、分布式,具有大数据处理能力的文档存储数据库。文档数据库MongoDB用于记录文档结构的数据,比如JSON、XML结构的数据。二、MongoDB的主要特征高性能。提供JSON、XML等可嵌入数据快速处理功能,提供文档的索引功能,以提高查询速度;丰富的查询语言。为数据聚合、结构文档、地理空间提供丰富的查询功能;高可用性。提供自动故障转移和数据冗余处理功能;水平扩展能力。提供基于多服务器集群的分布式数据处理能力,具体处理时分主从和权衡(基于Hash自动推选)两种处理模式;支持多种存储引擎。MongoDB提供多种存储引擎,WiredTiger引擎、MMAPv1引擎是基于硬盘读写的存储引擎,In-Memory引擎是基于内存的存储引擎;三、MongoDB优缺点,扬长避短1、优点Free-schema无模式文档,适应非结构化数据存储;内置GridFS,支持大容量的存储;内置Sharding,分片简单弱一致性(最终一致),更能保证用户的访问速度;查询性能优越,对于千万级别的文档对象,差不多10个G,对有索引的ID的查询不会比MySQL慢,而对非索引字段的查询,则是完胜MySQL;聚合框架,它支持典型几种聚合操作 , 比如,Aggregate pipelien, Map-Reduce等;支持自动故障恢复2、缺点太吃内存,快是有原因的,因为MongoDB把数据都放内存里了;对事务的支持不够友好;占用空间过大;对联表查询的支持不够强大;只有最终一致性,言外之意,就是可能造成数据的不一致,如果想要保持强一致性,必须在一个服务器处理所有的读写操作,坑;复杂聚合操作通过mapreduce创建,速度慢;Mongodb全局锁机制也是个坑;预分配模式会带来的磁盘瓶颈;删除记录时不会释放空间,相当于逻辑删除,这个真的坑;MongoDB到现在为止,好像还没有太好用的客户端工具;四、何时选择MongoDB?为啥要用它?1、MongoDB事务MongoDB目前只支持单文档事务,MongoDB暂时不适合需要复杂事务的场景。 灵活的文档模型JSON格式存储最接近真实对象模型,对开发者友好,方便快速开发迭代,可用复制集满足数据高可靠、高可用的需求,运维较为简单、故障自动切换可扩展分片集群海量数据存储。2、多引擎支持各种强大的索引需求支持地理位置索引可用于构建各种O2O应用文本索引解决搜索的需求TTL索引解决历史数据过期的需求Gridfs解决文件存储的需求aggregation & mapreduce解决数据分析场景需求,可以自己写查询语句或脚本,将请求分发到 MongoDB 上完成。3、具体的应用场景传统的关系型数据库在解决三高问题上的力不从心。 何为三高?High performance - 对数据库高并发读写的需求。Huge Storage - 对海量数据的高效率存储和访问的需求。High Scalability && High Availability- 对数据库的高可扩展性和高可用性的需求。MongoDB可以完美解决三高问题。4、以下是几个实际的应用案例:(1)游戏场景使用MongoDB存储游戏用户信息、装备、积分等,直接以内嵌文档的形式存储,方便查询、更新。(2)物流场景使用MongoDB存储订单信息、订单状态、物流信息,订单状态在运送过程中飞速迭代、以MongoDB内嵌数组的形式来存储,一次查询就能将订单所有的变更查出来,牛逼plus。(3)社交场景使用MongoDB存储用户信息,朋友圈信息,通过地理位置索引实现附近的人、定位功能。(4)物联网场景使用MongoDB存储设备信息、设备汇报的日志信息、并对这些信息进行多维度分析。(5)视频直播使用MongoDB存储用户信息、点赞互动信息。5、选择MongoDB的场景总结:数据量大读写操作频繁数据价值较低,对事务要求不高五、MongoDB与MySQL关键字对比1、关键字对比MySQLMongoDB解释说明databasedatabase数据库tablecollection表/集合rowdocument行/文档columnfield字段/域indexindex索引join嵌入文档表关联/MongoDB不支持join,MongoDB通过嵌入式文档来替代多表连接primary keyprimary key主键/MongoDB自动将_id字段设置为主键2、集合相当于MySQL中的表集合就是一组文档。可以看作是具有动态模式的表。集合具有动态模式的特性。这意味着一个集合中的文档可以具有任意数量的不同形态。但是,将不同类型的文档存放在一个集合中会出现很多问题:文档中可以存放任意类型的变量,但是,这里不建议将不同类型的文档保存在同一个集合中,开发人员需要确保每个查询只返回特定模式的文档,或者确保执行查询的应用程序代码可以处理不同类型的文档;获取集合列表比提取集合中的文档类型列表要快得多,减少磁盘查找次数;相同类型的文档存放在同一个集合中可以实现数据的局部性,对于集合,让使用者见文知意;集合中只存放单一类型的文档,可以更高效地对集合进行索引;3、集合的命名集合名称中不能是空字符串;集合名称不能包含\0(空字符),因为这个字符用于表示一个集合名称的结束;集合名称不能以system.开头,该前缀是为内部集合保留的。集合名称不能有$,只能在某些特定情况下使用。通常情况下,可以认为这两个字符是MongoDB的保留字符,如果使用不当,那么驱动程序将无法正常工作。4、文档相当于MySQL中的行文档是MongoDB中的基本数据单元,相当于传统关系型数据库中的行,它是一组有序键值的集合。每个文档都有一个特殊的键“_id”,其在所属的集合中是唯一的。文档中的键是字符串类型。键中不能含有\0(空字符)。这个字符用于表示一个键的结束。 .和$是特殊字符,只能在某些特定情况下使用。通常情况下,可以认为这两个字符是MongoDB的保留字符,如果使用不当,那么驱动程序将无法正常工作。5、游标数据库会使用游标返回find的执行结果。游标的客户端实现通常能够在很大程度上对查询的最终输出进行控制。你可以限制结果的数量,跳过一些结果,按任意方向的任意键组合对结果进行排序,以及执行许多其他功能强大的操作。通过cursor.hasNext()检查是否还有其它结果,通过cursor.next()用来对其进行获取。调用find()时,shell并不会立即查询数据库,而是等到真正开始请求结果时才发送查询,这样可以在执行之前给查询附加额外的选项。cursor对象的大多数方法会返回游标本身,这样就可以按照任意顺序将选项链接起来了。在使用db.users.find();查询时,实际上查询并没有真正执行,只是在构造查询,执行cursor.hasNext(),查询才会发往服务器端。shell会立刻获取前100个结果或者前4MB的数据(两者之中的较小者),这样下次调用next或者hasNext时就不必再次连接服务器去获取结果了。在客户端遍历完第一组结果后,shell会再次连接数据库,使用getMore请求更多的结果。getMore请求包含一个游标的标识符,它会向数据库询问是否还有更多的结果,如果有则返回下一批结果。这个过程会一直持续,直到游标耗尽或者结果被全部返回。6、游标的生命周期在服务器端,游标会占用内存和资源。一旦游标遍历完结果之后,或者客户端发送一条消息要求终止,数据库就可以释放它正在使用的资源。何时销毁游标:当游标遍历完匹配的结果时,它会消除自身;当游标超出客户端的作用域时,驱动程序会向数据库发送一条特殊的消息,让数据库终止该游标;如果10分钟没有被使用的话,数据库游标也将自动销毁;六、下载与安装过程中一些常见的坑1、下载地址:https://www.mongodb.com/try/download/community22、配置环境变量D:\Program Files\MongoDB\Server\5.0\bin3、在bin目录下,重新打开一个窗口,D:\Program Files\MongoDB\Server\5.0\bin,打开cmd,输入MongoDB4、如果msi方式失败,可以下载zip文件进行安装。下载zip文件,解压,在bin同级目录下建data文件夹,在data下建一个db文件夹,存储MongoDB数据。在bin文件夹下执行cmd,执行mongod --dbpath D:\Program Files\mongodb\data\db命令;再在data目录下,建一个logs文件夹,存放MongoDB日志。在mongodb/bin目录下,建一个mongod.cfg文件,写入systemLog:
destination: file
logAppend: true
path: D:\Program Files\mongodb\data\logs\mongod.log
storage:
dbPath: D:\Program Files\mongodb\data\db执行mongod --config "D:\Program Files\mongodb\bin\mongod.cfg" --install 命令,安装MongoDB。通过mongod --version检查MongoDB版本。D:\Program Files\mongodb\bin>mongod --version
db version v5.0.14
Build Info: {
"version": "5.0.14",
"gitVersion": "1b3b0073a0b436a8a502b612f24fb2bd572772e5",
"modules": [],
"allocator": "tcmalloc",
"environment": {
"distmod": "windows",
"distarch": "x86_64",
"target_arch": "x86_64"
}
}5、mongodb由于目标计算机积极拒绝,无法连接突然间,mongodb无法连接了?mongod.exe --dbpath "D:\Program Files\mongodb\data完美解决。注意一点,在重新启动时,执行mongod.exe --dbpath "D:\Program Files\mongodb\data的窗口不要关闭。6、由于找不到vcruntime140_1.dll,无法继续执行代码1、下载vcruntime140_1.dll文件2、将vcruntime140_1.dll文件拷贝到C:\Windows\System32即可七、Java整合MongoDB,实现农民工增删改查1、加入POM<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.8.2</version>
</dependency>2、MongoDBUtil工具类package com.example.demo.utils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.bson.Document;
import org.bson.conversions.Bson;
import com.mongodb.MongoClient;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
public class MongoDBUtil {
private static MongoClient mongoClient;
private static MongoClient mongoClientIdentify;
/**
* 不通过认证获取连接数据库对象
*/
public static MongoDatabase getNoIdentifyConnect(String host, int port, String dbaseName) {
// 连接mongodb服务
MongoDBUtil.mongoClient = new MongoClient(host, port);
// 连接数据库
MongoDatabase mongoDatabase = MongoDBUtil.mongoClient.getDatabase(dbaseName);
// 返回连接数据库对象
return mongoDatabase;
}
/**
* 通过连接认证获取MongoDB连接
*/
public static MongoDatabase getIdentifyConnect(String host, int port, String dbaseName, String userName, String password) {
List<ServerAddress> adds = new ArrayList<ServerAddress>();
ServerAddress serverAddress = new ServerAddress(host, port);
adds.add(serverAddress);
List<MongoCredential> credentials = new ArrayList<>();
MongoCredential mongoCredential = MongoCredential.createScramSha1Credential(userName, dbaseName, password.toCharArray());
credentials.add(mongoCredential);
// 通过连接认证获取MongoDB连接
MongoDBUtil.mongoClientIdentify = new MongoClient(adds, credentials);
MongoDatabase mongoDatabase = MongoDBUtil.mongoClientIdentify.getDatabase(dbaseName);
return mongoDatabase;
}
/**
* 关闭连接
*/
public static void closeNoIdentifyConnect () {
MongoDBUtil.mongoClient.close();
}
/**
* 关闭连接
*/
public static void closeIdentifyConnect () {
MongoDBUtil.mongoClientIdentify.close();
}
/**
* 插入一个文档
*/
public static void insertOne (Map<String, Object> data, MongoDatabase mongoDatabase, String col) {
//获取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
//创建文档
Document document = new Document();
for (Map.Entry<String, Object> m : data.entrySet()) {
document.append(m.getKey(), m.getValue()).append(m.getKey(), m.getValue());
}
//插入一个文档
collection.insertOne(document);
}
/**
* 插入多个文档
*/
public static void insertMany (List<Map<String, Object>> listData, MongoDatabase mongoDatabase, String col) {
//获取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
//要插入的数据
List<Document> list = new ArrayList<>();
for (Map<String, Object> data : listData) {
//创建文档
Document document = new Document();
for (Map.Entry<String, Object> m : data.entrySet()) {
document.append(m.getKey(), m.getValue());
}
list.add(document);
}
//插入多个文档
collection.insertMany(list);
}
/**
* 删除匹配到的第一个文档
*/
public static void delectOne (String col, String key, Object value, MongoDatabase mongoDatabase) {
//获取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
//申明删除条件
Bson filter = Filters.eq(key, value);
//删除与筛选器匹配的单个文档
collection.deleteOne(filter);
}
/**
* 删除匹配的所有文档
*/
public static void deleteMany (String col, String key, Object value, MongoDatabase mongoDatabase) {
//获取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
//申明删除条件
Bson filter = Filters.eq(key, value);
//删除与筛选器匹配的所有文档
collection.deleteMany(filter);
}
/**
* 删除集合中所有文档
*/
public static void deleteAllDocument(String col, MongoDatabase mongoDatabase) {
//获取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
collection.deleteMany(new Document());
}
/**
* 删除文档和集合。
*/
public static void deleteAllCollection(String col, MongoDatabase mongoDatabase) {
//获取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
collection.drop();
}
/**
* 修改单个文档,修改过滤器筛选出的第一个文档
*
* @param col 修改的集合
* @param key 修改条件的键
* @param value 修改条件的值
* @param eqKey 要修改的键,如果eqKey不存在,则新增记录
* @param eqValue 要修改的值
* @param mongoDatabase 连接数据库对象
*/
public static void updateOne (String col, String key, Object value,String eqKey, Object eqValue, MongoDatabase mongoDatabase) {
//获取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
//修改过滤器
Bson filter = Filters.eq(key, value);
//指定修改的更新文档
Document document = new Document("$set", new Document(eqKey, eqValue));
//修改单个文档
collection.updateOne(filter, document);
}
/**
* 修改多个文档
*
* @param col 修改的集合
* @param key 修改条件的键
* @param value 修改条件的值
* @param eqKey 要修改的键,如果eqKey不存在,则新增记录
* @param eqValue 要修改的值
* @param mongoDatabase 连接数据库对象
*/
public static void updateMany (String col, String key, Object value, String eqKey, Object eqValue, MongoDatabase mongoDatabase) {
//获取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
//修改过滤器
Bson filter = Filters.eq(key, value);
//指定修改的更新文档
Document document = new Document("$set", new Document(eqKey, eqValue));
//修改多个文档
collection.updateMany(filter, document);
}
/**
* 查找集合中的所有文档
*/
public static MongoCursor<Document> find (String col, MongoDatabase mongoDatabase) {
//获取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
//查找集合中的所有文档
FindIterable<Document> findIterable = collection.find();
MongoCursor<Document> cursorIterator = findIterable.iterator();
return cursorIterator;
}
/**
* 按条件查找集合中文档
*/
public static MongoCursor<Document> Filterfind (String col,String key, Object value, MongoDatabase mongoDatabase) {
//获取集合
MongoCollection<Document> collection = mongoDatabase.getCollection(col);
//指定查询过滤器
Bson filter = Filters.eq(key, value);
//指定查询过滤器查询
FindIterable<Document> findIterable = collection.find(filter);
MongoCursor<Document> cursorIterator = findIterable.iterator();
return cursorIterator;
}
}3、测试类<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>package com.example.demo.utils;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MongoDBTest {
// 获取数据库连接对象
MongoDatabase mongoDatabase = MongoDBUtil.getNoIdentifyConnect("127.0.0.1", 27017, "test");
@Test
public void insertOne() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("姓名", "哪吒编程");
map.put("性别", "男");
map.put("年龄", 18);
MongoDBUtil.insertOne(map, mongoDatabase, "worker");
MongoDBUtil.closeNoIdentifyConnect();
}
@Test
public void insertMany() {
Map<String, Object> map1 = new HashMap<String, Object>();
map1.put("姓名", "哪吒编程2");
map1.put("性别", "男");
map1.put("年龄", 18);
Map<String, Object> map2 = new HashMap<String, Object>();
map2.put("姓名", "妲己");
map2.put("性别", "女");
map2.put("年龄", 18);
List<Map<String, Object>> listData = new ArrayList<>();
listData.add(map1);
listData.add(map2);
MongoDBUtil.insertMany(listData, mongoDatabase, "worker");
MongoDBUtil.closeNoIdentifyConnect();
}
@Test
public void delectOne() {
MongoDBUtil.delectOne("worker", "姓名", "妲己", mongoDatabase);
MongoDBUtil.closeNoIdentifyConnect();
}
@Test
public void deleteMany() {
MongoDBUtil.deleteMany("worker", "姓名", "哪吒编程", mongoDatabase);
MongoDBUtil.deleteMany("worker", "姓名", "妲己", mongoDatabase);
MongoDBUtil.closeNoIdentifyConnect();
}
@Test
public void deleteAllDocument() {
MongoDBUtil.deleteAllDocument("worker", mongoDatabase);
MongoDBUtil.closeNoIdentifyConnect();
}
@Test
public void deleteAllCollection() {
MongoDBUtil.deleteAllCollection("worker", mongoDatabase);
MongoDBUtil.closeNoIdentifyConnect();
}
@Test
public void updateOne() {
MongoDBUtil.updateOne("worker", "姓名", "哪吒编程2","姓名", "哪吒编程", mongoDatabase);
MongoDBUtil.closeNoIdentifyConnect();
}
@Test
public void updateMany() {
MongoDBUtil.updateMany("worker", "姓名", "哪吒编程2","姓名", "哪吒编程", mongoDatabase);
MongoDBUtil.closeNoIdentifyConnect();
}
@Test
public void find() {
MongoCursor<Document> mongoCursor = MongoDBUtil.find("worker", mongoDatabase);
while (mongoCursor.hasNext()) {
Document document = mongoCursor.next();
System.out.println(document + " size: " + document.size());
}
MongoDBUtil.closeNoIdentifyConnect();
}
@Test
public void filterfind() {
MongoCursor<Document> mongoCursor = MongoDBUtil.Filterfind("worker", "姓名", "哪吒编程", mongoDatabase);
while (mongoCursor.hasNext()) {
Document document = mongoCursor.next();
System.out.println(document + " size: " + document.size());
}
MongoDBUtil.closeNoIdentifyConnect();
}
}
无事小神仙
MongoDB 高可用与高性能指南
本专栏将深入阐述 MongoDB 与 MySQL 的差异和优势,分享一次线上事故中的顿悟和 MongoDB 高可用的喜爱体会,介绍 MongoDB 4.0 支持事务的新特性,探讨如何解决 MongoDB 超大块数据问题和数据库性能监控等关键话题。通过学习本专栏,读者将能够掌握 MongoDB 的高可用和高性能技巧和最佳实践,更好地应用 MongoDB 来解决实际问题,提升自己的工作和生活水平。
无事小神仙
【MySql】MySQL索引15连问(相信大家看完肯定会有帮助)
索引是什么?索引是一种能提高数据库查询效率的数据结构。它可以比作一本字典的目录,可以帮你快速找到对应的记录。索引一般存储在磁盘的文件中,它是占用物理空间的。正所谓水能载舟,也能覆舟。适当的索引能提高查询效率,过多的索引会影响数据库表的插入和更新功能。2. MySQL索引有哪些类型数据结构维度B+树索引:所有数据存储在叶子节点,复杂度为O(logn),适合范围查询。哈希索引: 适合等值查询,检索效率高,一次到位。全文索引:MyISAM和InnoDB中都支持使用全文索引,一般在文本类型char,text,varchar类型上创建。R-Tree索引: 用来对GIS数据类型创建SPATIAL索引物理存储维度聚集索引:聚集索引就是以主键创建的索引,在叶子节点存储的是表中的数据。(Innodb存储引擎)非聚集索引:非聚集索引就是以非主键创建的索引,在叶子节点存储的是主键和索引列。(Innodb存储引擎)逻辑维度主键索引:一种特殊的唯一索引,不允许有空值。普通索引:MySQL中基本索引类型,允许空值和重复值。联合索引:多个字段创建的索引,使用时遵循最左前缀原则。唯一索引:索引列中的值必须是唯一的,但是允许为空值。空间索引:MySQL5.7之后支持空间索引,在空间索引这方面遵循OpenGIS几何数据模型规则。3. 索引什么时候会失效?查询条件包含or,可能导致索引失效如果字段类型是字符串,where时一定用引号括起来,否则索引失效like通配符可能导致索引失效。联合索引,查询时的条件列不是联合索引中的第一个列,索引失效。在索引列上使用 mysql 的内置函数,索引失效。对索引列运算(如,+、-、*、/),索引失效。索引字段上使用(!= 或者 < >,not in)时,可能会导致索引失效。索引字段上使用is null, is not null,可能导致索引失效。左连接查询或者右连接查询查询关联的字段编码格式不一样,可能导致索引失效。mysql 估计使用全表扫描要比使用索引快,则不使用索引。4. 哪些场景不适合建立索引?数据量少的表,不适合加索引更新比较频繁的也不适合加索引区分度低的字段不适合加索引(如性别)where、group by、order by等后面没有使用到的字段,不需要建立索引已经有冗余的索引的情况(比如已经有a,b的联合索引,不需要再单独建立a索引)5. 为什么要用 B+树,为什么不用二叉树?可以从几个维度去看这个问题,查询是否够快,效率是否稳定,存储数据多少, 以及查找磁盘次数,为什么不是二叉树,为什么不是平衡二叉树,为什么不是 B 树,而偏偏是 B+树呢?为什么不是一般二叉树?如果二叉树特殊化为一个链表,相当于全表扫描。平衡二叉树相比于二叉查找 树来说,查找效率更稳定,总体的查找速度也更快。为什么不是平衡二叉树呢?我们知道,在内存比在磁盘的数据,查询效率快得多。如果树这种数据结构作为索引,那我们每查找一次数据就需要从磁盘中读取一个节点,也就是我们说的一个磁盘块,但是平衡二叉树可是每个节点只存储一个键值和数据的,如果是 B 树,可以存储更多的节点数据,树的高度也会降低,因此读取磁盘的次数就降下来啦,查询效率就快啦。那为什么不是 B 树而是 B+树呢?B+树非叶子节点上是不存储数据的,仅存储键值,而 B 树节点中不仅存储键值,也会存储数据。innodb 中页的默认大小是 16KB,如果不存储数据,那 么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就 会更矮更胖,如此一来我们查找数据进行磁盘的 IO 次数有会再次减少,数据查询的效率也会更快。B+树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的,链表连着的。那么 B+树使得范围查找,排序查找,分组查找以及去重查找变得 异常简单。6. 一次B+树索引树查找过程假设有以下表结构,并且初始化了这几条数据CREATE TABLE `employee` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`date` datetime DEFAULT NULL,
`sex` int(1) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_age` (`age`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into employee values(100,'小伦',43,'2021-01-20','0');
insert into employee values(200,'俊杰',48,'2021-01-21','0');
insert into employee values(300,'紫琪',36,'2020-01-21','1');
insert into employee values(400,'立红',32,'2020-01-21','0');
insert into employee values(500,'易迅',37,'2020-01-21','1');
insert into employee values(600,'小军',49,'2021-01-21','0');
insert into employee values(700,'小燕',28,'2021-01-21','1');
执行这条查询SQL,需要执行几次的树搜索操作?可以画下对应的索引树结构图~select * from Temployee where age=32;
其实这个,这个大家可以先画出idx_age普通索引的索引结构图,大概如下:再画出id主键索引,我们先画出聚族索引结构图,如下:这条 SQL 查询语句执行大概流程是这样的:搜索idx_age 索引树,将磁盘块1加载到内存,由于32<43,搜索左路分支,到磁盘寻址磁盘块2。将磁盘块2加载到内存中,由于32<36,搜索左路分支,到磁盘寻址磁盘块4。将磁盘块4加载到内存中,在内存继续遍历,找到age=32的记录,取得id = 400.拿到id=400后,回到id主键索引树。搜索id主键索引树,将磁盘块1加载到内存,因为300<400<500,所以在选择中间分支,到磁盘寻址磁盘块3。虽然在磁盘块3,找到了id=400,但是它不是叶子节点,所以会继续往下找。 到磁盘寻址磁盘块8。将磁盘块8加载内存,在内存遍历,找到id=400的记录,拿到R4这一行的数据,好的,大功告成。7. 什么是回表?如何减少回表?当查询的数据在索引树中,找不到的时候,需要回到主键索引树中去获取,这个过程叫做回表。比如在第6小节中,使用的查询SQLselect * from employee where age=32;
需要查询所有列的数据,idx_age普通索引不能满足,需要拿到主键id的值后,再回到id主键索引查找获取,这个过程就是回表。8. 什么是覆盖索引?如果我们查询SQL的select * 修改为 select id, age的话,其实是不需要回表的。因为id和age的值,都在idx_age索引树的叶子节点上,这就涉及到覆盖索引的只是点了。覆盖索引是select的数据列只用从索引中就能够取得,不必回表,换句话说,查询列要被所建的索引覆盖。9. 聊聊索引的最左前缀原则索引的最左前缀原则,可以是联合索引的最左N个字段。比如你建立一个组合索引(a,b,c),其实可以相当于建了(a),(a,b),(a,b,c)三个索引,大大提高了索引复用能力。当然,最左前缀也可以是字符串索引的最左M个字符。。 比如,你的普通索引树是酱紫:这个SQL: select * from employee where name like '小%' order by age desc; 也是命中索引的。10. 索引下推了解过吗?什么事索引下推给你这个SQL:select * from employee where name like '小%' and age=28 and sex='0';
其中,name和age为联合索引(idx_name_age)。如果是Mysql5.6之前,在idx_name_age索引树,找出所有名字第一个字是“小”的人,拿到它们的主键id,然后回表找出数据行,再去对比年龄和性别等其他字段。如图:有些朋友可能觉得奇怪,idx_name_age(name,age)不是联合索引嘛?为什么选出包含“小”字后,不再顺便看下年龄age再回表呢,不是更高效嘛?所以呀,MySQL 5.6就引入了索引下推优化,可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。因此,MySQL5.6版本之后,选出包含“小”字后,顺表过滤age=2811. 大表如何添加索引如果一张表数据量级是千万级别以上的,那么,如何给这张表添加索引?我们需要知道一点,给表添加索引的时候,是会对表加锁的。如果不谨慎操作,有可能出现生产事故的。可以参考以下方法:先创建一张跟原表A数据结构相同的新表B。在新表B添加需要加上的新索引。把原表A数据导到新表Brename新表B为原表的表名A,原表A换别的表名;12. 如何知道语句是否走索引查询?explain查看SQL的执行计划,这样就知道是否命中索引了。当explain与SQL一起使用时,MySQL将显示来自优化器的有关语句执行计划的信息。一般来说,我们需要重点关注type、rows、filtered、extra、key。1.2.1 typetype表示连接类型,查看索引执行情况的一个重要指标。以下性能从好到坏依次:system > const > eq_ref > ref > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALLsystem:这种类型要求数据库表中只有一条数据,是const类型的一个特例,一般情况下是不会出现的。const:通过一次索引就能找到数据,一般用于主键或唯一索引作为条件,这类扫描效率极高,,速度非常快。eq_ref:常用于主键或唯一索引扫描,一般指使用主键的关联查询ref : 常用于非主键和唯一索引扫描。ref_or_null:这种连接类型类似于ref,区别在于MySQL会额外搜索包含NULL值的行index_merge:使用了索引合并优化方法,查询使用了两个以上的索引。unique_subquery:类似于eq_ref,条件用了in子查询index_subquery:区别于unique_subquery,用于非唯一索引,可以返回重复值。range:常用于范围查询,比如:between … and 或 In 等操作index:全索引扫描ALL:全表扫描1.2.2 rows该列表示MySQL估算要找到我们所需的记录,需要读取的行数。对于InnoDB表,此数字是估计值,并非一定是个准确值。1.2.3 filtered该列是一个百分比的值,表里符合条件的记录数的百分比。简单点说,这个字段表示存储引擎返回的数据在经过过滤后,剩下满足条件的记录数量的比例。1.2.4 extra该字段包含有关MySQL如何解析查询的其他信息,它一般会出现这几个值:Using filesort:表示按文件排序,一般是在指定的排序和索引排序不一致的情况才会出现。一般见于order by语句Using index :表示是否用了覆盖索引。Using temporary: 表示是否使用了临时表,性能特别差,需要重点优化。一般多见于group by语句,或者union语句。Using where : 表示使用了where条件过滤.Using index condition:MySQL5.6之后新增的索引下推。在存储引擎层进行数据过滤,而不是在服务层过滤,利用索引现有的数据减少回表的数据。1.2.5 key该列表示实际用到的索引。一般配合possible_keys列一起看。13.Hash 索引和 B+树区别是什么?你在设计索引是怎么抉择的?B+树可以进行范围查询,Hash 索引不能。B+树支持联合索引的最左侧原则,Hash 索引不支持。B+树支持 order by 排序,Hash 索引不支持。Hash 索引在等值查询上比 B+树效率更高。(但是索引列的重复值很多的话,Hash冲突,效率降低)。B+树使用 like 进行模糊查询的时候,like 后面(比如%开头)的话可以起到优化的作用,Hash 索引根本无法进行模糊查询。14. 索引有哪些优缺点?优点:索引可以加快数据查询速度,减少查询时间唯一索引可以保证数据库表中每一行的数据的唯一性缺点:创建索引和维护索引要耗费时间索引需要占物理空间,除了数据表占用数据空间之外,每一个索引还要占用一定的物理空间以表中的数据进行增、删、改的时候,索引也要动态的维护。15. 聚簇索引与非聚簇索引的区别聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。它表示索引结构和数据一起存放的索引。非聚集索引是索引结构和数据分开存放的索引。接下来,我们分不同存存储引擎去聊哈~在MySQL的InnoDB存储引擎中, 聚簇索引与非聚簇索引最大的区别,在于叶节点是否存放一整行记录。聚簇索引叶子节点存储了一整行记录,而非聚簇索引叶子节点存储的是主键信息,因此,一般非聚簇索引还需要回表查询。一个表中只能拥有一个聚集索引(因为一般聚簇索引就是主键索引),而非聚集索引一个表则可以存在多个。一般来说,相对于非聚簇索引,聚簇索引查询效率更高,因为不用回表。而在MyISM存储引擎中,它的主键索引,普通索引都是非聚簇索引,因为数据和索引是分开的,叶子节点都使用一个地址指向真正的表数据。
无事小神仙
Oracle将SQL查询结果生成为HTML网页文件(图文记录详细步骤)
方式一:1、表数据生成为html格式内容的CLOB对象2、把CLOB的信息转成文件(HTML)方式二:命令行执行批处理程序生成HTML文件下面详细来看:方式一的实现:一、登录Oracle数据库创建临时表scott_emp,作为本次演练使用。scott_emp该表就是本次演练中的数据表,根据scott.emp表来创建。二、创建函数sql_to_html_xslt(),输入要执行的SQL和标题,即可将SQL查询结果返回为html格式内容的CLOB对象。2.1、创建函数sql_to_html_xslt()有两种方式创建函数,一是从SQL窗口中执行SQL脚本创建函数,二是在命令行执行脚本文件来创建。两者的区别是,SQL窗口中执行SQL脚本中html中表示空格的转义符号“ ”需要改成“& ”,即多一个“&”符号。2.1.1、SQL窗口执行SQL脚本创建函数sql_to_html_xslt()可以直接用PL/SQL打开一个SQL窗口,复制下面的SQL脚本然后执行:create or replace function sql_to_html_xslt(p_sql in varchar2, p_title varchar2 default null)
return clob as
/*
Copyright DarkAthena(darkathena@qq.com)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* author:DarkAthena
name:query sql to a html-table (with xslt)
date:2021-10-28
EMAIL:darkathena@qq.com
example 1:
select sql_to_html_xslt(Q'{select * from job_history}') html_table
from dual;
example 2:
select sql_to_html_xslt(Q'{select * from job_history}','this is title') html_table
from dual;
*/
l_ctx dbms_xmlgen.ctxhandle;
l_num_rows pls_integer;
l_xml xmltype;
l_html xmltype;
l_returnvalue clob;
l_xml_to_html_stylesheet varchar2(4000);
l_css varchar2(4000);
begin
l_xml_to_html_stylesheet := q'^<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<table border="1">
<xsl:apply-templates select="ROWSET/ROW[1]" />
</table>
</xsl:template>
<xsl:template match="ROW">
<tr><xsl:apply-templates mode="th" /></tr>
<xsl:apply-templates select="../ROW" mode="td" />
</xsl:template>
<xsl:template match="ROW/*" mode="th">
<th><xsl:value-of select="local-name()" /></th>
</xsl:template>
<xsl:template match="ROW" mode="td">
<tr><xsl:apply-templates /></tr>
</xsl:template>
<xsl:template match="ROW/*">
<td><xsl:apply-templates /></td>
</xsl:template>
</xsl:stylesheet>^';
l_css := '<style type=''text/css''>
body {font:10pt Arial,Helvetica,sans-serif; color:black; background:White;}
p {font:10pt Arial,Helvetica,sans-serif; color:black; background:White;}
table,tr,td {font:10pt Arial,Helvetica,sans-serif; color:Black; background:#f7f7e7; padding:0px 0px 0px 0px; margin:0px 0px 0px 0px;}
th {font:bold 10pt Arial,Helvetica,sans-serif; color:#336699; background:#cccc99; padding:0px 0px 0px 0px;}
h1 {font:16pt Arial,Helvetica,Geneva,sans-serif; color:#336699; background-color:White; border-bottom:1px solid #cccc99; margin-top:0pt; margin-bottom:0pt; padding:0px 0px 0px 0px;}
h2 {font:bold 10pt Arial,Helvetica,Geneva,sans-serif; color:#336699; background-color:White; margin-top:4pt; margin-bottom:0pt;}
a {font:9pt Arial,Helvetica,sans-serif; color:#663300; background:#ffffff; margin-top:0pt; margin-bottom:0pt; vertical-align:top;}
</style>'; ---CSS ---- from SQLPLUS spool html style
l_returnvalue := '<!DOCTYPE HTML><html><head><body>' || l_css || '<h1>' ||
p_title || '</h1>';
l_ctx := dbms_xmlgen.newcontext(p_sql);
dbms_xmlgen.setnullhandling(l_ctx, dbms_xmlgen.empty_tag);
l_xml := dbms_xmlgen.getxmltype(l_ctx, dbms_xmlgen.none);
--dbms_output.put_line(l_xml.getClobVal());
l_num_rows := dbms_xmlgen.getnumrowsprocessed(l_ctx);
dbms_xmlgen.closecontext(l_ctx);
if l_num_rows > 0 then
l_html := l_xml.transform(xmltype(l_xml_to_html_stylesheet));
dbms_lob.append(l_returnvalue, l_html.getclobval());
end if;
dbms_lob.append(l_returnvalue, '</body>' || chr(10) || '</html>');
return replace(l_returnvalue,' <td/>',' <td>& </td>');
exception
when others then
raise;
end;
/
2.1.2、在命令行执行脚本文件sql_to_html_xslt.fnc创建函数sql_to_html_xslt()2.1.2.1、网盘下载脚本文件:sql_to_html_xslt.fnc网盘下载:https://pan.baidu.com/s/1LHgwPqa01_Ig07deJP6vZA?pwd=yyds2.1.2.2、命令行执行脚本文件创建函数sql_to_html_xslt()下载好文件(sql_to_html_xslt.fnc)后,用PL/SQL打开一个命令行窗口,输入命令:@D:\tmp\sql_to_html_xslt.fnc(注意:实际的目录名称根据自己的文件位置修改,文件所在目录不要有空格)。执行过程即结果如下图所示:2.2、调用函数sql_to_html_xslt()2.2.1、执行SQL查询(带title参数查询)SELECT sql_to_html_xslt(q'{select * from scott_emp}', 'sql_to_html') FROM dual;2.2.2、执行SQL查询(不带title参数查询)SELECT sql_to_html_xslt(q'{select * from scott_emp}') FROM dual;三、以管理员身份登录,创建目录html_dir并授权给当前需要使用的用户。以 “sys@TZQ AS SYSDBA” 身份登录,执行创建目录,并授权给当前使用的用户:CREATE OR REPLACE DIRECTORY html_dir AS 'D:\tmp\html';
GRANT READ,WRITE ON DIRECTORY html_dir TO LOG;
四、创建存过clob_to_file(),把CLOB的信息转成文件(HTML)4.1、创建存过clob_to_file()clob_to_file()存过代码如下:CREATE OR REPLACE PROCEDURE clob_to_file(p_dir IN VARCHAR2,
p_file IN VARCHAR2,
p_clob IN CLOB) AS
l_output utl_file.file_type;
l_amt NUMBER DEFAULT 32000;
l_offset NUMBER DEFAULT 1;
l_length NUMBER DEFAULT nvl(dbms_lob.getlength(p_clob)
,0);
BEGIN
l_output := utl_file.fopen(p_dir
,p_file
,'w'
,32760);
WHILE (l_offset < l_length) LOOP
utl_file.put(l_output
,dbms_lob.substr(p_clob
,l_amt
,l_offset));
utl_file.fflush(l_output);
l_offset := l_offset + l_amt;
END LOOP;
utl_file.new_line(l_output);
utl_file.fclose(l_output);
END;
/
4.2、调用存过clob_to_file(),将CLOB内容生成HTML文件。执行下面匿名块,在 *D:\tmp\html* 目录下生成HTML文件 html_test_001.html :DECLARE
v_clob CLOB;
BEGIN
-- 参考上面3.2步骤的使用,返回CLOB字段的HTML内容给到临时变量 v_clob
SELECT sql_to_html_xslt(q'{select * from scott_emp}')
INTO v_clob
FROM dual;
clob_to_file('HTML_DIR' -- 目录
,'html_test_001.html' -- 文件名
,v_clob); -- CLOB字段的值 v_clob
END;
执行结果如下:4.3、查看HTML文件。查看 *D:\tmp\html* 目录下的HTML文件 html_test_001.html 内容:方式二的实现:一、创建SQL文件:D:\tmp\html.sqlset feedback off
set markup html on;
SET MARKUP HTML ON SPOOL ON PREFORMAT OFF ENTMAP ON HEAD "<TITLE>Department Report</TITLE> <STYLE type='text/css'> <!-- BODY {background: #FFFFC6} --> </STYLE>" BODY "TEXT='#FF00Ff'" TABLE "WIDTH='90%' BORDER='5'"
define data_path=D:\tmp\scripts
col ymd new_value v_ymd
select to_char(sysdate,'YYYYMMDDHH24MISS') ymd FROM dual;
spool D:\tmp\scripts\result_html_&&v_ymd..html
select * from scott.emp;
spool off
set markup html off
exit
二、创建批处理BAT程序:D:\tmp\sql_to_html.batsqlplus log/1@tzq @D:\tmp\html.sql > D:\tmp\html.txt
exit;三、双击文件(D:\tmp\sql_to_html.bat),运行批处理BAT程序会调用命令行窗口,然后一闪而过,代表BAT批处理程序已经执行完了。四、查看生成的HTML文件
无事小神仙
MySQL SQL的完整处理流程
一、sql执行流程分析一条sql从客户端发起,在mysql中经过了一系列的流程,归结为如下图所示:客户端提交一条sql语句,先在查询缓存中查询,如果缓存没有命中,将会进行查表操作。查表的流程总结过为如下:(1)将sql交给解析器处理,生成一个解析树。(2)预处理器会处理解析器,重新生成一个解析器,这个过程中将会改写sql。(3)改写后的解析器交给查询优化器,查询优化器生成sql的执行计划。(4)执行计划交给执行引擎调用存储引擎的的API接口,查询数据。(5)最终的结果由执行引擎返回给客户端,如果看,开启查询缓存的话将会返回给客户端。二、sql执行顺序总结FROM <left_table><join_type> JOIN <right_table>ON <join_condition>WHERE <where_condition>GROUP <group_by_list>WITH {CUBE|ROLLUP}HAVING <having_condition>SELECTDISTINCT <select_list>ORDER BY <order_by_list>LIMIT <limit_number>三、查询优化器与执行计划1、查询优化器查询优化器的主要作用是用来生成sql的执行计划,查询优化器是数据库的核心大脑所在,从某种意义上来说,优化sql,本质是理解优化器的执行行为。在mysql中优化的依据是sql的执行成本,执行计划的生成是基于成本的,成本的决定是依据sql的执行行数。优化器工作的前提是了解数据,工作的目的是解析数据,生成执行计划。2、查询优化器执行过程如上图所示:查询优化器有语法分析,词法分析,语义检查等过程。预处理阶段(查询改写)查询优化阶段:主要有逻辑优化,物理优化。3.1. 逻辑优化:把sql交给查询优化器之后,会根据生成的解析树,对sql做一些改写操作。3.2. 物理优化;过程是优化器生成获取数据去扫描数据的路径。查询优化器优化依据,来自于代价估算器估算结果(它会调用统计信息作为计算依据)交由执行器执行。四、查看和干预sql执行计划执行计划查看执行计划可以采用explain关键字。EXPLAIN SELECT * from addr_longxi
当我们发现sql的执行计划不合理时,可以通过添加索引和强制驱动表的顺序,通过hints方式干预sql的执行计划。另外,mysql优化器的一些参数也可以进行修改,来控制优化器的一些行为。2. 优化器开关·show variables like 'optimizer_switch
3.Processlist另一种观测MySQL行为的常用手段就是Processlist。通过Processlist,我们可以看到当前在MySQL中执行的所有SQL语句,有没有异常的会话或比较特殊的SQL状态。查看会话操作可以通过2种途径:第一,show [full] processlist;第二,information_schema.processlist。常见异常行为有:Coping to tmp table:出现在某些Alter Table语句的Copy Table操作.Copying to tmp table on disk:由于临时结果集大于tmp_table_size 将临时表中的数据转换到磁盘中Converting HEAP to MyISAM:线程正在从内部内存临时表到磁盘MYISA临时表Creating sort index:正在使用内部临时表处理Select查询。Sorting index:磁盘排序操作的一个过程。Sending data:将结果正在发送给客户端。
无事小神仙
MySQL 分库分表实践
一、为什么要分库分表数据库架构演变刚开始多数项目用单机数据库就够了,随着服务器流量越来越大,面对的请求也越来越多,我们做了数据库读写分离, 使用多个从库副本(Slave)负责读,使用主库(Master)负责写,master和slave通过主从复制实现数据同步更新,保持数据一致。slave 从库可以水平扩展,所以更多的读请求不成问题但是当用户量级上升,写请求越来越多,怎么保证数据库的负载足够?增加一个Master是不能解决问题的, 因为数据要保存一致性,写操作需要2个master之间同步,相当于是重复了,而且架构设计更加复杂这时需要用到分库分表(sharding),把库和表存放在不同的MySQL Server上,每台服务器可以均衡写请求的次数二、库表太大产生的问题单库太大:单库处理能力有限、所在服务器上的磁盘空间不足、遇到IO瓶颈,需要把单库切分成更多更小的库单表太大:CRUD效率都很低,数据量太大导致索引文件过大,磁盘IO加载索引花费时间,导致查询超时。所以只用索引还是不行的,需要把单表切分成多个数据集更小的表。 MyCat提供的分表算法都在rule.xml,可以根据不同的分表算法进行拆分,比如根据时间拆分、一致性哈希、直接用主键对分表的个数取模等拆分策略单个库太大,先考虑是表多还是数据多:如果因为表多而造成数据过多,则使用垂直拆分,即根据业务拆分成不同的库如果因为单张表的数据量太大,则使用水平拆分,即把表的数据按照某种规则(mycat/conf/rule.xml定义的分表算法)拆分成多张表分库分表的原则应该是先考虑垂直拆分,再考虑水平拆分三、垂直拆分分库分表和读写分离可以共同进行1. 垂直分库server.xml<user name="root">
<property name="password">123456</property>
<property name="schemas">USERDB1,USERDB2</property>
</user>
配置了USERDB1、USERDB2这两个逻辑库schema.xml<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- 逻辑数据库 -->
<schema name="USERDB1" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1" /> <!-- 两个逻辑库对应两个不同的数据节点 -->
<schema name="USERDB2" checkSQLschema="false" sqlMaxLimit="100"dataNode="dn2" />
<!-- 存储节点 -->
<dataNode name="dn1" dataHost="node1" database="mytest1" /> <!-- 两个数据节点对应两个不同的物理机器 -->
<dataNode name="dn2" dataHost="node2" database="mytest2" /> <!-- USERDB1对应mytest1,USERDB2对应mytest2 -->
<!-- 数据库主机 -->
<dataHost name="node1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native">
<heartbeat>select user()</heartbeat>
<writeHost host="192.168.131.129" url="192.168.131.129:3306" user="root" password="123456" />
</dataHost>
<dataHost name="node2" maxCon="1000" minCon="10" balance="0"writeType="0" dbType="mysql" dbDriver="native">
<heartbeat>select user()</heartbeat>
<writeHost host="192.168.0.6" url="192.168.0.6:3306" user="root" password="123456" />
</dataHost>
</mycat:schema>
两个逻辑库对应两个不同的数据节点,两个数据节点对应两个不同的物理机器mytest1和mytest2分成了不同机器上的不同的库,各包含一部分表,它们原来是合在一块的,在一台机器上,现在做了垂直的拆分。客户端就需要去连接不同的逻辑库了,根据业务操作不同的逻辑库然后配置了两个写库,两台机器把库平分了,分担了原来单机的压力。分库伴随着分表,从业务上对表拆分2. 垂直分表垂直分表,基于列字段进行。一般是针对几百列的这种大表,也避免查询时,数据量太大造成的“跨页”问题。一般是表中的字段较多,将不常用的, 数据较大,长度较长(比如text类型字段)的拆分到扩展表。访问频率较高的字段单独放在一张表四、水平分表针对数据量巨大的单张表(比如订单表),按照某种规则(RANGE、HASH取模等),切分到多张表里面去。 但是这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈,不建议采用将单张表的数据切分到多个服务器上去,每个服务器具有一部分库与表,只是表中数据集合不同。 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈分库分表可以和主从复制同时进行,但不基于主从复制;读写分离才基于主从复制1. 配置水平分表server.xml<user name="root">
<property name="password">123456</property>
<property name="schemas">USERDB</property>
</user>
schema.xml<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- 逻辑数据库 -->
<schema name="USERDB" checkSQLschema="false" sqlMaxLimit="100">
<table name="user" dataNode="dn1" /> <!-- 这里的user和student都是实际存在的物理表名 -->
<table name="student" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2" rule="mod-long"/> <!-- student表按照id取模拆分 -->
</schema>
<!-- 存储节点 -->
<dataNode name="dn1" dataHost="node1" database="mytest1" />
<dataNode name="dn2" dataHost="node2" database="mytest2" />
<!-- 数据库主机 -->
<dataHost name="node1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native">
<heartbeat>select user()</heartbeat>
<writeHost host="192.168.131.129" url="192.168.131.129:3306" user="root" password="123456" />
</dataHost>
<dataHost name="node2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native">
<heartbeat>select user()</heartbeat>
<writeHost host="192.168.0.6" url="192.168.0.6:3306" user="root" password="123456" />
</dataHost>
</mycat:schema>
user表示一个普通的表,直接放在数据节点dn1上,放在一台机器上,这张表不用进行拆分student表的primaryKey是id,根据id拆分,放在dn1和dn2上,最终这个表要分在两台机器上,在物理上分开了,但是在逻辑上还是一个,往哪张表里增加,在2台机器上查询然后如何合并这些操作都是由mycat完成的拆分的规则是取模(mod - long),每次插入用id模上存在的机器数(2)此外还需要在rule.xml中配置以下拆分算法找到算法mod-long,因为我们将逻辑表student分开映射到两台主机上,所以修改数据节点的数量为22. 测试水平分表Linux主机Windows主机登录到mycat的8066端口使用MyCat给user表插入两条数据由于schema.xml配置文件中,逻辑表user只在Linux主机的mytest1库中存在,mycat操作的逻辑表user会影响Linux主机上的物理表,而不会影响Windows主机上的表。我们分别查看一下Linux和Windows主机的user表:我们再通过MyCat给student表插入两条数据我们知道schema.xml配置文件中,逻辑表student对应两台主机上的两个库mytest1、mytest2中的两张表,所以对逻辑表插入的两条数据,会实际影响到两张物理表(用id%机器数,决定插入到哪张物理表)。我们分别查看一下Linux和Windows主机的student表:再通过MyCat插入id=3和id=4的数据,应该插入不同主机上的不同物理表这就相当于把student表进行水平拆分了通过MyCat查询的时候只需要正常输入就行,我们配置的是表拆分后放在这2个数据节点上,MyCat会根据配置在两个库上查询并进行数据合并
无事小神仙
MySQL数据库分库分表方案
前言随着项目不断迭代,使用人数的不断增加。数据库中某些表数据正在逐步膨胀,往单表千万迅速靠拢。所以最近领导也在考虑做一下分库分表,写此文章记录下来。一、什么是分库分表?分库:从单个数据库拆分成多个数据库的过程,将数据散落在多个数据库中。分表:从单张表拆分成多张表的过程,将数据散落在多张表内。二、为什么分库分表?随着平台的业务发展,数据可能会越来越多,甚至达到亿级。以MySQL为例,单库数据量在5000万以内性能比较好,超过阈值后性能会随着数据量的增大而明显降低。单表的数据量超过1000w,性能也会下降严重。这就会导致查询一次所花的时间变长,并发操作达到一定量时可能会卡死,甚至把系统给拖垮。三、怎么选择分库分表策略?切分方案解决的问题只分库不分表数据库读?写QPS过高,数据库连接数不足只分表不分库单表数据过大,存储性能遇到瓶颈即分库又分表连接数不足+数据量过大引起的存储性能瓶颈四、分库分表方式及带来的问题?分库分表有效的缓解了大数据、高并发带来的性能和压力,也能突破网络IO、硬件资源、连接数的瓶颈,但同时也带来了一些问题。4.1、事务一致性问题由于分库分表把数据分布在不同库甚至不同服务器,不可避免会带来分布式事务问题,我们需要额外编程解决该问题。4.2、跨节点join在没有进行分库分表前,我们检索商品时可以通过以下SQL对店铺信息进行关联查询:SELECT p.*,s.[店铺名称],s.[信誉]
FROM [商品信息] p
LEFT JOIN [店铺信息] s ON p.id = s.[所属店铺]
WHERE...ORDER BY...LIMIT...
但经过分库分表后,[商品信息]和[店铺信息]不在一个数据库或一个表中,甚至不在一台服务器上,无法通过sql语句进行关联查询,我们需要额外编程解决该问题。4.3、跨节点分页、排序和聚合函数跨节点多库进行查询时,limit分页、order by排序以及聚合函数等问题,就变得比较复杂了。需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序。例如,进行水平分库后的商品库,按ID倒序排序分页,取第一页:以上流程是取第一页的数据,性能影响不大,但由于商品信息的分布在各数据库的数据可能是随机的,如果是取第N页,需要将所有节点前N页数据都取出来合并,再进行整体的排序,操作效率可想而知,所以请求页数越大,系统的性能也会越差。在使用Max、Min、Sum、Count之类的函数进行计算的时候,与排序分页同理,也需要先在每个分片上执行相应的函数,然后将各个分片的结果集进行汇总和再次计算,最终将结果返回。4.4、主键避重在分库分表环境中,由于表中数据同时存在不同数据库中,主键值平时使用的自增长将无用武之地,某个分区数据库生成的ID无法保证全局唯一。因此需要单独设计全局主键,以避免跨库主键重复问题。由于分库分表之后,数据被分散在不同的服务器、数据库和表中。因此,对数据的操作也就无法通过常规方式完成,并且它还带来了一系列的问题。我们在开发过程中需要通过一些中间件解决这些问题,市面上有很多中间件可供我们选择,其中Sharding-JDBC和mycat较为流行。五、使用分库分表组件帮我们解决一些问题分库分表的技术方案总体上来讲分为两大类:应用层依赖类中间件、中间层代理类中间件。我们选择技术方案时主要考虑的是,开源、开发成本、学习成本、技术复杂度,技术使用人数,参考资料的多少等方面。由于我本人也不是每样技术都有用过。所以在这里只是在能力范围内做一个初步了解,并进行选型。目前这些组件对于分库分表的一些主要问题都有相对完善的解决方案,区别的只是一些细节的问题。又结合目前项目所在只需要轻量级的分库分表。所以我还是比较偏向成本较低,复杂度较低的方案。目前市面上使用较多的是,mycat及sharding-jdbc。mycat属于中间层代理类中间件、sharding-jdbc属于应用层依赖类中间件5.1. Atlas奇虎360关键词:分库分表 Atlas百度为您找到相关结果约707,000个中间层代理类中间件github.com/Qihoo360/At…github上最后维护时间为4年前优点 实现了读写分离(并通过hint/master/可强制⾛主库,并且加⼊了权重配置可进⾏读的负载均衡 ⾃⾝维护了⼀套连接池,减少了创建连接带来的性能消耗 ⽀持DB动态上下线,⽅便横向扩展 ⽀持ip过滤,实现了简单的权限控制 可记录所有sql,实现了简单的审计功能缺点 使⽤atlas⽐直连DB,性能损耗⼤概是30%-35%左右 使⽤atlas⽐直连DB,响应时间⼤概是直连DB的1.5~2倍 对分表的⽀持不是太好,不支持不同库间分表 atlas配置暂时不⽀持配置参数的动态加载,如果修改了配置需要重启atlas,这可能会对业务有⼀点的影响(不过⼀般可以做ha或者业务低峰进⾏重启,这个问题不是特别迫切)总的来说作为⼀款开源mysql proxy,atlas总体表现还是不错的,持续压测3天都⽐较稳定,只是对分表的⽀持不是太好(⽐如不⽀持基于时间的分表模式),⼀般没有太⾼并发和对响应时间严格要求的业务可以考虑尝试使⽤5.2. Cobar阿里关键词:分库分表 Cobar百度为您找到相关结果约936,000个中间层代理类中间件github.com/alibaba/cob…github上最后维护时间为3年前槽点貌似很多,讲不完哈哈www.modb.pro/db/1752425.3. TDDL阿里关键词:分库分表 TDDL百度为您找到相关结果约1,080,000个应用层依赖类中间件github.com/alibaba/tb_…github上TDDL处于停滞状态,应该是部分功能不开源了吧。TDDL 必须要依赖 diamond 配置中心( diamond 是淘宝内部使用的一个管理持久配置的系统,目前淘宝内部绝大多数系统的配置)5.4. heisenberg百度关键词:分库分表 heisenberg百度为您找到相关结果约74,900个中间层代理类中间件资料少之又少,不考虑。5.5. Oceanus58同城关键词:分库分表 Oceanus百度为您找到相关结果约118,000个github.com/wuba/Oceanu…github上最后维护时间为3年前资料较少,不考虑。5.6. OneProxy原支付宝首席架构师楼方鑫开发关键词:分库分表 OneProxy百度为您找到相关结果约139,000个应该是不开源的5.7. vitessYouTube关键词:分库分表 vitess百度为您找到相关结果约177,000个中间层代理类中间件github.com/vitessio/vi…github当前活跃Vitess是一个用于部署、扩展和管理大型MySQL实例集群的数据库解决方案。是开源的,在github上有很多星星,但是国内的应用较少,资料不多。技术架构复杂,这位更是重量级。5.8. TSharding蘑菇街关键词:分库分表 TSharding百度为您找到相关结果约100,000个github.com/baihui212/t…github上最后维护时间为5年前应该也是不开源了资料少之又少,不考虑5.9. dal携程关键词:分库分表 dal百度为您找到相关结果约315,000个应用层依赖类中间件github.com/ctripcorp/d…github当前活跃,提供部分教程及demo(需要科学上网)开源范围包括代码生成器,Java客户端和C#客户端。国内资料少,需要科学上网。5.10. zdal支付宝关键词:分库分表 zdal百度为您找到相关结果约30,600个中间层代理类中间件国内资料少,应该不开源。5.11.MyCat基于cobar社区开源关键词:分库分表 MyCat百度为您找到相关结果约9,030,000个中间层代理类中间件mycatone.top/社区当前活跃,无需科学上网资料很多也开源,可以考虑。5.11.1不支持项DDL语句 不支持修改拆分键 支持物理库的视图视为普通表来使用 仅普通表支持外键 DML语句DELETE语句 不支持涉及分布式运算的子查询。 不支持多表delete。UPDATE语句 不支持涉及分布式运算的子查询。 不支持多表update。SELECT语句 对于for update语句会把sql中出现的表都加锁。 具体是行锁还是表锁要看sql语句。 不支持SELECT INTO OUTFILE。SET语句 支持SET SESSION级别的变量,但是不能被预处理语句引用变量,只有autocommit变量具有正确语义 不支持SET GLOBAL级别的变量 不支持SET USER级别的变量SHOW语句 所有SHOW语句都视为兼容性SQL进行处理,发往prototype节点处理,所以不具备分布式语义高级功能 不支持用户自定义数据类型(改代码), 自定义函数(改代码) 支持物理视图,但是不支持Mycat中的逻辑视图 有限支持存储过程 不支持游标 不支持触发器5.12.Sharding-jdbc当当开源,已加入apache豪华套餐关键词:分库分表 Sharding-jdbc百度为您找到相关结果约4,240,000个应用层依赖类中间件shardingsphere.apache.org/社区当前活跃,无需科学上网资料很多也开源,可以考虑。更多参考之前写的:分库分表 Sharding-JDBC5.12.1不支持项DataSource 接口 不支持 timeout 相关操作。Connection 接口 不支持存储过程,函数,游标的操作; 不支持执行 native SQL; 不支持 savepoint 相关操作; 不支持 Schema/Catalog 的操作; 不支持自定义类型映射。Statement 和 PreparedStatement 接口 不支持返回多结果集的语句(即存储过程,非 SELECT 多条数据); 不支持国际化字符的操作。ResultSet 接口 不支持对于结果集指针位置判断; 不支持通过非 next 方法改变结果指针位置; 不支持修改结果集内容; 不支持获取国际化字符; 不支持获取 Array。JDBC 4.1 不支持 JDBC 4.1 接口新功能。六、详细比较主要指标Sharding-jdbcMycatORM支持适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC任意事务自带XA、两(三)阶段事务、柔性事务BASE(最终一致)XA事务分库支持支持分表支持支持开发集成springboot较好,代码入侵中(需要写些配置类等)开发成本小,代码入侵小所属公司当当网开源,加入apache基于阿里Cobar二次开发,社区维护数据库支持支持任意实现 JDBC 规范的数据库,目前支持 MySQL,PostgreSQL,Oracle,SQLServer 以及任何可使用 JDBC 访问的数据库Mysql、Oracle、 SQL Server、DB2、mongodb活跃度活跃度高社区活跃度很高,一些公司已在使用监控有有读写分离支持支持资料资料少、github、官网、网上讨论贴资料多,github、官网、Q群、书籍运维维护成本低维护成本高限制部分JDBC方法不支持、SQL语句限制SQL语句限制连接池支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, HikariCP 等无要求配置难度一般复杂总结挑选了两个使用最多的进行了比较,综合来看的话感觉还是Sharding-jdbc更省事一些,无需部署中间件,只通过引入jar包进行分库分表操作,省去一些事情。而且多一个中间件的话系统稳定性也会降低了。对与目前来说,只需要轻量级的分库分表,功能不需要太多,所以还是选择Sharding-jdbc合适些。
无事小神仙
热点数据更新导致CPU100%的解决方案
前言在平常的工作中,更新数据是再正常不过的一个需求了,我们只需要执行一个update语句即可,如果有必要我们还可以加上事务来保证数据的可靠性。但是如果这是一个热点数据,就比如说直播下单,如果这个商品很火爆,价格又很低,那么就会有很多人下单,这种下单不像秒杀,秒杀是有限制数量,但是这种直播下单是不限量的,可以同时有很多人在下单,这时候用户来下单,那么首先就要判断库存是否足够,如果有库存,那么就更新库存。这时候,这个库存就成了热点数据,因为如果有几万人同时下单,那么就会导致同时有几万个线程来更新这个库存数据。这时候我们的CPU就会瞬间达到100%。就有可能出现一些异常情况,导致用户下单业务受到影响。我们可以使用本地数据库,加上jmeter来进行压测,因为之前一次压测,我把公司测试数据库都搞崩了,所以这里没有展示cpu信息了。大家可以自己进行压测,然后使用top命令来查看cpu的信息。为什么会导致CPU飙升这时候就要谈到MySql的行锁了。在我们执行一条update语句的时候,这时候MySql会开启一个事务,并且对这条记录进行加锁。在这个线程没有释放资源以前,其它线程进来要更新就只能等待。但是这并不是导致CPU飙升的原因。我们知道MySql是有一个死锁检测的机制,也就是说,当一个线程去更新记录的时候,首先要判断是否会发生死锁,如果发生死锁,就会主动回滚某一个事务,让其释放资源,让其它事务得以继续运行。所以这时候问题就来了,如果这时候有1000个线程,那么每个线程都要去判断是否会发生死锁,也就是要每一个线程都要跟其它线程进行一个死锁的判断。那么这个时间复杂度就是O(n2), 也就是有1000 * 1000 = 1000000,100万次的死锁判断,就是因为有了这个死锁检测,所以才导致CPU飙升。那么有什么办法去解决嘛?当然是有的解决方案- 分而治之我们可以采用分而治之的思想去解决这个问题。比如我们现在有1万个库存,那么我们可以搞10台服务器,也就是10台MySql服务器。每台服务器上都只放1000个库存,这样我们就可以将压力分摊在这10台服务器。这时候很多人会问,即使分摊给多台服务器,那么MySql的压力还是很大呀。是的,所以在高并发的写场景下,我们不建议使用MySql,而是使用缓存数据库,比如Redis这时候我们将所有的库存都放在一个redis中,为了保证数据的可靠性,我们还是用了主从备份,甚至是redis集群等。但是用户下单的都是同一个商品,也就是说所有用户来访问都只会是访问同一个redis节点。即使你使用了redis这种缓存数据库,当并发量上来,redis还是会扛不住的。所以我们需要使用redis的分片技术,也就是将库存分摊到多个redis节点。跟MySql一样,也搞10台redis服务。这样就可以将压力分摊到10个redis节点上。流量分摊思路假设我们现在就有10台MySql服务器。那么如何将用户流量分摊到这10台服务器呢?我们知道,用户下单那么订单里面肯定会有一个用户ID,所以我们可以对用户ID进行取模,这样就可以将用户流量分摊到每台服务器上但是这时候又会有一个问题,如果这时候第一台MySql的库存很快就没有了,其它MySql上还有库存这时候如果这个用户到MySql-1上发现没有库存了,这时候我们可以让他到MySql-2服务器上,以此类推,直到有库存返回即可当然使用redis的思路也是如此。总结在平常工作中,如果碰到大数据量,或者大流量的时候,分而治之是一个很好的解决思路,将数据或者流量分摊,以此来减轻每台服务器的压力。
无事小神仙
【技术选型】Mysql和ES数据同步方案汇总
背景在实际项目开发中,我们经常将Mysql作为业务数据库,ES作为查询数据库,用来实现读写分离,缓解Mysql数据库的查询压力,应对海量数据的复杂查询。这其中有一个很重要的问题,就是如何实现Mysql数据库和ES的数据同步,今天和大家聊聊Mysql和ES数据同步的各种方案。一、Mysql和ES各自的特点为什么选用MysqlMySQL 在关系型数据库历史上并没有特别优势的位置,Oracle/DB2/PostgreSQL(Ingres) 三老比 MySQL 开发早了 20 来年, 但是乘着 2000 年的互联网东风, LAMP 架构得到迅速的使用,特别在中国,大部分新兴企业的 IT 系统主数据沉淀于 MySQL 中。核心特点:开源免费、高并发、稳定、支持事务、支持SQL查询高并发能力:MySQL 内核特征特别适合高并发简单 SQL 操作 ,链接轻量化(线程模式),优化器、执行器、事务引擎相对简单粗暴,存储引擎做得比较细致稳定性好:主数据库最大的要求就是稳定、不丢数据,MySQL 内核特征反倒让其特点鲜明,从而达到很好的稳定性,主备系统也很早就 ready ,应对崩溃情况下的快速切换,innodb 存储引擎也保障了 MySQL 下盘稳定操作便捷:良好、便捷的用户体验(相比 PostgreSQL) , 让应用开发者非常容易上手 ,学习成本较低开源生态:MySQL 是一款开源产品,让上下游厂商围绕其构建工具相对简单,HA proxy、分库分表中间件让其实用性大大加强,同时开源的特质让其有大量的用户为什么选用 ESES 几个显著的特点,能够有效补足 MySQL 在企业级数据操作场景的缺陷,而这也是我们将其选择作为下游数据源重要原因核心特点:支持分词检索,多维筛选性能好,支持海量数据查询文本搜索能力:ES 是基于倒排索引实现的搜索系统,配合多样的分词器,在文本模糊匹配搜索上表现得比较好,业务场景广泛多维筛选性能好:亿级规模数据使用宽表预构建(消除 join),配合全字段索引,使 ES 在多维筛选能力上具备压倒性优势,而这个能力是诸如 CRM, BOSS, MIS 等企业运营系统核心诉求,加上文本搜索能力,独此一家开源和商业并行:ES 开源生态非常活跃,具备大量的用户群体,同时其背后也有独立的商业公司支撑,而这让用户根据自身特点有了更加多样、渐进的选择二、数据同步方案1、同步双写这是一种最为简单的方式,在将数据写到mysql时,同时将数据写到ES。伪代码:/**
* 新增商品
*/
@Transactional(rollbackFor = Exception.class)
public void addGoods(GoodsDto goodsDto) {
//1、保存Mysql
Goods goods = new Goods();
BeanUtils.copyProperties(goodsDto,goods);
GoodsMapper.insert();
//2、保存ES
IndexRequest indexRequest = new IndexRequest("goods_index","_doc");
indexRequest.source(JSON.toJSONString(goods), XContentType.JSON);
indexRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
highLevelClient.index(indexRequest);
}
优点: 业务逻辑简单 实时性高缺点: 硬编码,有需要写入mysql的地方都需要添加写入ES的代码; 业务强耦合; 存在双写失败丢数据风险; 性能较差:本来mysql的性能不是很高,再加一个ES,系统的性能必然会下降。附: 上面说的双写失败风险,包括以下几种: ES系统不可用; 程序和ES之间的网络故障; 程序重启,导致系统来不及写入ES等。针对这种情况,有数据强一致性要求的,就必须双写放到事务中来处理,而一旦用上事物,则性能下降更加明显。2、异步双写(MQ方式)针对多数据源写入的场景,可以借助MQ实现异步的多源写入,这种情况下各个源的写入逻辑互不干扰,不会由于单个数据源写入异常或缓慢影响其他数据源的写入,虽然整体写入的吞吐量增大了,但是由于MQ消费是异步消费,所以不适合实时业务场景。优点: 性能高 不易出现数据丢失问题,主要基于MQ消息的消费保障机制,比如ES宕机或者写入失败,还能重新消费MQ消息。 多源写入之间相互隔离,便于扩展更多的数据源写入缺点: 硬编码问题,接入新的数据源需要实现新的消费者代码 系统复杂度增加:引入了消息中间件 可能出现延时问题:MQ是异步消费模型,用户写入的数据不一定可以马上看到,造成延时。3、基于Mysql表定时扫描同步上面两种方案中都存在硬编码问题,也就是有任何对mysq进行增删改查的地方要么植入ES代码,要么替换为MQ代码,代码的侵入性太强。如果对实时性要求不高的情况下,可以考虑用定时器来处理,具体步骤如下:数据库的。m字段,任何crud操作都会导致该字段的时间发生变化;原来程序中的CURD操作不做任何变化;增加一个定时器程序,让该程序按一定的时间周期扫描指定的表,把该时间段内发生变化的数据提取出来;逐条写入到ES中。如下图所示:该方案的典型实现是借助logstash实现数据同步,其底层实现原理就是根据配置定期使用sql查询新增的数据写入ES中,实现数据的增量同步。具体实现可以参考:通过Logstash实现mysql数据定时增量同步到ES优点: 不改变原来代码,没有侵入性、没有硬编码; 没有业务强耦合,不改变原来程序的性能; Worker代码编写简单不需要考虑增删改查;缺点: 时效性较差,由于是采用定时器根据固定频率查询表来同步数据,尽管将同步周期设置到秒级,也还是会存在一定时间的延迟。 对数据库有一定的轮询压力,一种改进方法是将轮询放到压力不大的从库上。4、基于Binlog实时同步上面三种方案要么有代码侵入,要么有硬编码,要么有延迟,那么有没有一种方案既能保证数据同步的实时性又没有代入侵入呢?当然有,可以利用mysql的binlog来进行同步。其实现原理如下:具体步骤如下: 读取mysql的binlog日志,获取指定表的日志信息; 将读取的信息转为MQ; 编写一个MQ消费程序; 不断消费MQ,每消费完一条消息,将消息写入到ES中。优点: 没有代码侵入、没有硬编码; 原有系统不需要任何变化,没有感知; 性能高; 业务解耦,不需要关注原来系统的业务逻辑。缺点: 构建Binlog系统复杂; 如果采用MQ消费解析的binlog信息,也会像方案二一样存在MQ延时的风险。5、业界目前较为流行的方案:使用canal监听binlog同步数据到escanal ,译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费。 说白了就是,根据Mysql的binlog日志进行增量同步数据。要理解canal的原理,就要先了解mysql的主从复制原理: 所有的create update delete操作都会进入MySQLmaster节点 master节点会生成binlog文件,每次操作mysql数据库就会记录到binlog文件中 slave节点会订阅master节点的binlog文件,以增量备份的形式同步数据到slave数据canal原理就是伪装成mysql的从节点,从而订阅master节点的binlog日志,主要流程为: canal服务端向mysql的master节点传输dump协议 mysql的master节点接收到dump请求后推送binlog日志给canal服务端,解析binlog对象(原始为byte流)转成Json格式 canal客户端通过TCP协议或MQ形式监听canal服务端,同步数据到ES三、数据迁移同步工具选型数据迁移同步工具的选择比较多样,下表仅从 MySQL 同步 ES 这个场景下,对一些笔者深度使用研究过的数据同步工具进行对比,用户可以根据自己的实际需要选取适合自己的产品。特性\产品CanalDTSCloudCanal是否支持自建ES是否是ES对端版本支持丰富度中 支持ES6和ES7高 支持ES5,ES6和ES7中 支持ES6和ES7嵌套类型支持join/nested/objectobjectnested/objectjoin支持方式基于join父子文档&反查无基于宽表预构建&反查是否支持结构迁移否是是是否支持全量迁移是是是是否支持增量迁移是是是数据过滤能力中 -仅全量可添加where条件高 -全增量阶段where条件高 -全增量阶段where条件是否支持时区转换否是是同步限流能力无有有任务编辑能力无有无数据源支持丰富度中高中架构模式订阅消费模式 需先写入消息队列直连模式直连模式监控指标丰富度中 性能指标监控中 性能指标监控高 性能指标、资源指标监控报警能力无 针对延迟、异常的电话报警针对延迟、异常的钉钉、短信、邮件报警任务可视化创建&配置&管理能力无有有是否开源是否否是否免费是否 是社区版、SAAS版免费是否支持独立输出是否依赖云平台整体输出是是否支持SAAS化使用否是是总结本文主要对Mysql和ES进行数据同步的常见方案进行了汇总说明。同步双写是最简单的同步方式,能最大程度保证数据同步写入的实时性,最大的问题是代码侵入性太强。异步双写引入了消息中间件,由于MQ都是异步消费模型,所以可能出现数据同步延迟的问题。好处是在大规模消息同步时吞吐量更、高性能更好,便于接入更多的数据源,且各个数据源数据消费写入相互隔离互不影响。基于Mysql表定时扫描同步 ,原理是通过定时器定时扫描表中的增量数据进行数据同步,不会产生代码侵入,但由于是定时扫描同步,所以也会存在数据同步延迟问题,典型实现是采用 Logstash 实现增量同步。基于Binlog实时同步 ,原理是通过监听Mysql的binlog日志进行增量同步数据。不会产生代码侵入,数据同步的实时也能得到保障,弊端是Binlog系统都较为复杂。典型实现是采用 canal 实现数据同步。
无事小神仙
MySQL8.1.0版本正式发布带来哪些新特性?
前言2023年7月18日晚,MySQL官方网站正式发布了MySQL8.1.0与8.0.34版本.一、畅谈新版本Oracle 每季度发布一次 MySQL 更新,所以自己也一直留意的官网的动态,2023年7月18日晚,MySQL官方网站正式发布了MySQL8.1.0与8.0.34版本,8.1的应该来说是8.0版本的bug修复,这是 MySQL 变更发版模型后的第一个创新版本 (Innovation Release)。MySQL 8.0.34+ 将成为仅错误修复版本(红色)。MySQL大约每2年发布一个新的长期支持版本(如下图蓝色),一个例外是8.x LTS版本,它将在 8.0 的 EOL 之前发生,按照此规则来说,下一个(创新)版本将增加主版本号,如果 MySQL 8.4.0 是 8.x LTS 版本,那么 MySQL 9.0 将是下一个创新版本。官方下载文档:dev.mysql.com/downloads/m… 官方指南:dev.mysql.com/doc/refman/…二、8.1.0版本部署官方的下载渠道已开放,dev.mysql.com/downloads/m… RPM包下载选择Linux - Generic,适配对应的操作系统GCC版本。2.1、环境准备操作系统[root@jeames ~]# cat /etc/redhat-release
Red Hat Enterprise Linux release 8.1 (Ootpa)
关闭防火墙#查看防火墙是状态[root@jeames ~]# systemctl status firewalld
关闭防火墙[root@jeames ~]# systemctl stop firewalld
取消开机自启动[root@jeames ~]# systemctl disable firewalld
Removed /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
selinux关闭 修改参数文件/etc/sysconfig/selinux中SELINUX的值为disabled[root@jeames ~]# sed -i ‘s/SELINUX=enforcing/SELINUX=disabled/g’ /etc/selinux/config
修改完成后需要重启服务器才生效2.2、配置yum安装依赖创建挂载路径mkdir -p /mnt/cdrom
挂载系统镜像光盘到指定目录#因为光盘的格式通常是iso9660,意思是/dev/sr0挂载在/mnt/cdrom目录上
mount -t iso9660 /dev/sr0 /mnt/cdrom
mount: /mnt/cdrom: WARNING: device write-protected, mounted read-only.
修改yum源配置文件 ##编辑rhel8-local.repo文件,加入以下内容[root@jeames ~]# cd /etc/yum.repos.d
[root@jeames yum.repos.d]# rm -rf *
[root@jeames yum.repos.d]# vi rhel8-local.repo
[localREPO]
name=localhost8
baseurl=file:///mnt/cdrom/BaseOS
enable=1
gpgcheck=0
[localREPO_APP]
name=localhost8_app
baseurl=file:///mnt/cdrom/AppStream
enable=1
gpgcheck=0
配置好后重建本地缓存yum clean all
yum makecache
yum repolist
安装MySQL 8.1版本二进制所需的依赖包yum -y install libncurses*
yum -y install libaio
yum -y install perl perl-devel
yum -y install autoconf
yum -y install numactl.x86_64
##通过rpm -qa可以查询是否将依赖包安装成功
[root@jeames yum.repos.d]# rpm -qa libaio perl perl-devel autoconf \ numactl.x86_64 libncurses
2.3、用户及目录创建注:可以部署多个实例,通过端口区分root 用户操作:mkdir -p /mysql/data/mysql3306
mkdir -p /mysql/app/
mkdir -p /mysql/conf/
mkdir -p /mysql/data/mysql3306/pid/
mkdir -p /mysql/data/mysql3306/socket/
mkdir -p /mysql/data/mysql3306/log/
mkdir -p /mysql/data/mysql3306/binlog/
mkdir -p /mysql/data/mysql3306/errlog
mkdir -p /mysql/data/mysql3306/relaylog/
mkdir -p /mysql/data/mysql3306/slowlog/
mkdir -p /mysql/data/mysql3306/tmp/
2.4、创建用户及组[root@jeames ~]# groupadd mysql
[root@jeames ~]# useradd -g mysql mysql
[root@jeames ~]# chown -R mysql:mysql /mysql
[root@jeames ~]# passwd mysql
Changing password for user mysql.
New password:
BAD PASSWORD: The password is shorter than 8 characters
Retype new password:
passwd: all authentication tokens updated successfully.
[root@jeames ~]# cat /etc/group | grep mysql
mysql❌1001:
[root@jeames ~]# cat /etc/passwd | grep mysql
mysql❌1001:1001::/home/mysql:/bin/bash
2.5、解压缩包[root@jeames ~]# cd /opt
[root@jeames opt]# cp mysql-8.1.0-linux-glibc2.28-x86_64.tar.xz /mysql/app
[root@jeames opt]# chown -R mysql:mysql /mysql
mysql用户操作[root@jeames ~]# su - mysql
md5 值验证,保证下载到的软件包无破损无木马
[mysql@jeames ~]$ cd /mysql/app
[mysql@jeames app]$ ll
total 457200
-rw-r--r-- 1 mysql mysql 468170248 Jul 19 15:29 mysql-8.1.0-linux-glibc2.28-x86_64.tar.xz
[mysql@jeames app]$ md5sum mysql-8.1.0-linux-glibc2.28-x86_64.tar.xz
5285185fad2b4352c878380a741e29f0 mysql-8.1.0-linux-glibc2.28-x86_64.tar.xz
解压软件包并重命名[mysql@jeames app]$ tar xvf mysql-8.1.0-linux-glibc2.28-x86_64.tar.xz
[mysql@jeames app]$ mv mysql-8.1.0-linux-glibc2.28-x86_64 mysql8.1.0
[mysql@jeames app]$ ll
total 457200
drwxrwxr-x 9 mysql mysql 129 Jul 19 15:31 mysql8.1.0
-rw-r--r-- 1 mysql mysql 468170248 Jul 19 15:29 mysql-8.1.0-linux-glibc2.28-x86_64.tar.xz
2.6、环境变量配置##mysql用户下操作
cat >> /home/mysql/.bash_profile << “EOF”
MYSQL_HOME=/mysql/app/mysql8.1.0
PATH=P A T H : PATH:PATH:HOME/.local/bin:H O M E / b i n : HOME/bin:HOME/bin:MYSQL_HOME/bin
EOF
环境变量生效[mysql@jeames app]$ source ~/.bash_profile
[mysql@jeames app]$ which mysql
/mysql/app/mysql8.1.0/bin/mysql
2.7、创建参数文件由于是二进制文件安装,数据库参数文件需要自己配置cat >> /mysql/conf/my3306.cnf << "EOF"
[mysqld]
server_id = 8103306
default-storage-engine= InnoDB
basedir=/mysql/app/mysql8.1.0
datadir=/mysql/data/mysql3306/data/
socket=/mysql/data/mysql3306/socket/mysql.sock
log-error=/mysql/data/mysql3306/log/mysqld.log
pid-file=/mysql/data/mysql3306/pid/mysqld.pid
port=3306
default-time_zone='+8:00'
default_authentication_plugin=mysql_native_password # 加此参数可远程登陆
transaction_isolation=READ-COMMITTED
max_connections=1500
back_log=500
wait_timeout=1800
max_user_connections=800
innodb_buffer_pool_size=1024M
innodb_log_file_size=512M
innodb_log_buffer_size=40M
slow_query_log=ON
long_query_time=5
# log settings #
slow_query_log = ON
slow_query_log_file = /mysql/data/mysql3306/slowlog/slow3306.log
log_error = /mysql/data/mysql3306/errlog/err3306.log
log_error_verbosity = 3
log_bin = /mysql/data/mysql3306/binlog/mysql_bin
log_bin_index = /mysql/data/mysql3306/binlog/mysql_binlog.index
general_log_file = /data/mysql/mysql3306/generallog/general.log
log_queries_not_using_indexes = 1
log_slow_admin_statements = 1
expire_logs_days = 90
binlog_expire_logs_seconds = 2592000 #30d
long_query_time = 2
min_examined_row_limit = 100
log_throttle_queries_not_using_indexes = 1000
innodb_flush_log_at_trx_commit=1
EOF
2.8、数据库初始化mysql用户操作:mysqld --defaults-file=/mysql/conf/my3306.cnf --initialize --user=mysql --basedir=/mysql/app/mysql8.1.0 --datadir=/mysql/data/mysql3306/data/
注意:以上同一行执行2.9、启动Mysql[mysql@jeames app]$ mysqld_safe --defaults-file=/mysql/conf/my3306.cnf --user=mysql &
2.10、登陆MySQL 8.1第一次登录 MySQL 时,需要到错误日志下找初始化密码,使用 socket 加密码登录进去后,无法查询任何东西,提示先要修改 root 密码.[mysql@jeames ~]$ cat /mysql/data/mysql3306/errlog/err3306.log | grep password
[mysql@jeames ~]$ mysql -uroot -p
Enter password:
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)
[mysql@jeames ~]$ mysql -uroot -p -P 3306 -S /mysql/data/mysql3306/socket/mysql.sock
mysql> status
ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.
修改密码mysql> alter user root@'localhost' identified by 'root';
mysql> status
--------------
mysql Ver 8.1.0 for Linux on x86_64 (MySQL Community Server - GPL)
Connection id: 8
Current database:
Current user: root@localhost
SSL: Not in use
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server version: 8.1.0
Protocol version: 10
Connection: Localhost via UNIX socket
Server characterset: utf8mb4
Db characterset: utf8mb4
Client characterset: utf8mb4
Conn. characterset: utf8mb4
UNIX socket: /mysql/data/mysql3306/socket/mysql.sock
Binary data as: Hexadecimal
Uptime: 3 min 46 sec
Threads: 2 Questions: 8 Slow queries: 0 Opens: 398 Flush tables: 3 Open tables: 36 Queries per second avg: 0.035
远程登陆设置mysql> create user root@'%' identified with mysql_native_password by 'root';
Query OK, 0 rows affected (0.01 sec)
mysql> grant all on *.* to root@'%' with grant option;
Query OK, 0 rows affected (0.00 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
mysql> select user,host,plugin from mysql.user;
+------------------+-----------+-----------------------+
| user | host | plugin |
+------------------+-----------+-----------------------+
| root | % | mysql_native_password |
| mysql.infoschema | localhost | caching_sha2_password |
| mysql.session | localhost | caching_sha2_password |
| mysql.sys | localhost | caching_sha2_password |
| root | localhost | mysql_native_password |
+------------------+-----------+-----------------------+
5 rows in set (0.01 sec)
三、新特性3.1、密码参数增加了一个新的系统参数,用于限制用户改密码时的最少替换字符数。validate_password.changed_characters_percentage,该值是百分比。3.2、错误日志加强随着 MySQL 服务器、插件和组件的启动和关闭消息的增加,关闭过程的日志记录得到了增强。这些消息现在也被记录为关闭连接。这些附加功能应该有助于故障排除和调试问题,特别是在服务器需要很长时间才能关闭的情况下。此版本引入了一系列新的消息,这些消息会在MySQL错误日志中记录,包括以下内容:MySQLserver的启动和关闭日志消息,包括使用–initialize参数启动时的日志。插件关闭阶段的启动和结束日志消息。组件关闭阶段的启动和结束日志消息。连接关闭阶段的开始和结束日志消息。在强制断开连接后仍然活动的线程数量和ID的日志消息,这些线程可能会导致等待情况。mysql> show variables like 'log_error';
+---------------+------------------------------------------+
| Variable_name | Value |
+---------------+------------------------------------------+
| log_error | /mysql/data/mysql3306/errlog/err3306.log |
+---------------+------------------------------------------+
3.3、二进制日志libmysqlclient.so 共享库中添加了几个函数,使开发人员能够访问 MySQL server的二进制日志:mysql_binlog_open()、mysql_binlog_fetch() 和 mysql_binlog_close()。3.4、审计日志审计日志可以指定库database存储JSON过滤表。新增Audit_log_direct_writes系统变量,用于计算直接写入审计文件的次数。MySQL企业审计使用临时缓冲区保存写入日志文件的查询事件数据。然而,由于server可能无法为长查询分配额外内存,审计插件已经进行了优化,在JSON格式记录日志时,不再使用临时缓冲区。MySQL企业审计现在支持使用调度程序组件来配置和执行定期任务,以刷新内存缓存。mysql -u root -D database_name -p < audit_log_filter_linux_install.sql3.5、组复制增强添加了一些特定于组复制插件的状态变量,这些变量可以改进对网络不稳定的诊断和故障排除,为每个组成员 (group member) 提供有关网络使用情况、控制消息和数据消息的统计信息。作为这项工作的一部分,Performance Schema 的 replication_group_communication_information 表中添加了一个新列 MEMBER_FAILURE_SUSPICIONS_COUNT 。该列的内容被格式化为一个 JSON 数组,其键是组成员 ID,其值是该组成员被认为是可疑的次数。3.6、废弃特性以下功能在 MySQL 8.1 中已弃用,并且可能会在未来的系列中删除。如果显示替代方案,则应更新应用程序以使用它们。mysqlpump 由于 MySQL 提供了具有相同或额外功能的 mysqldump 和 MySQL Shell 等其他方法来执行数据库转储和备份, mysqlpump 已经变得多余,现在已弃用。现在调用这个程序会产生一个警告。您应该记住,mysqlpump 可能会在 MySQL 的未来版本中被删除。binlog_format binlog_format 服务器系统变量现已弃用,并且可能会在 MySQL 的未来版本中删除。 与此变量关联的功能(更改二进制日志记录格式)也已被弃用。 此更改的含义是,当删除 binlog_format 时,MySQL 服务器将仅支持基于行的二进制日志记录(MySQL 8.0 中的默认设置)。 因此,新安装应仅使用基于行 (Row) 的二进制日志记录,而使用基于语句或混合日志记录格式的现有安装应迁移到基于行的格式。 此更改的含义是,当删除 binlog_format 时,MySQL 服务器将仅支持基于行的二进制日志记录(MySQL 8.0 中的默认设置)。 因此,新安装应仅使用基于行 (Row) 的二进制日志记录,而使用基于语句或混合日志记录格式的现有安装应迁移到基于行的格式。 系统变量 log_bin_trust_function_creators 和 log_statements_unsafe_for_binlog 仅在基于语句的日志记录上下文中有用,现在也已弃用,因此也将在 MySQL 的未来版本中删除。 现在设置或选择刚才提到的任何变量的值都会引发警告。 (WL#13966,WL#15669)mysql_native_password mysql_native_password 身份验证插件现已弃用,并可能在 MySQL 的未来版本中删除。 如果帐户尝试使用 mysql_native_password 作为身份验证方法进行身份验证, CREATE USER、ALTER USER 和 SET PASSWORD 操作现在会在服务器错误日志中插入弃用警告。
无事小神仙
MySQL库表操作以及简单查询语句
一、结构化查询语句SQL是结构化查询语言,它是关系型数据库的通用语言。SQL主要可以分为一下三种类型:DDL(Data Definition Languages)语句:数据定义语句,这些语句定义了不同的数据库、表、列、索引等对象。常用的语句关键字有create、drop、alterDML(Data Manipulation Languages)语句:数据操作语句,用于添加、删除、更新和查询数据库记录,并检查数据完整性,常用的语句关键字包括 insert、delete、update和select等DCL(Data Control Languages)语句:数据控制语句,用于控制不同的许可和访问级别的语句。这些语句定义了数据库、表、字段、用户的访问权限和安全级别,常用的语句关键字包括grant、revoke二、库操作1. 查询数据库:
show databases;
2. 创建数据库
create database testdb;
3. 删除数据库
drop database testdb;
4. 选择数据库
use testdb;
三、表操作因为业务层操作内存,MySQL操作磁盘,数据库永远是最先达到性能瓶颈,我们不能把过多的逻辑操作放在数据库上,逻辑操作应该在业务层做。MySQL只做最核心的CRUD,触发器、存储函数、存储过程等都不会在MySQL上设置,统一迁移到业务层中的服务层做1. 创建表:
create table stu(
id int unsigned primary key not null auto_increment,
name varchar(50) not null unique,
age tinyint not null,
sex enum("man","woman") not null
);
2. 查看表结构:
desc stu;
3. 删除表:
drop table stu;
4. 打印表创建的SQL:
show create table stu;
四、CRUD操作1. 插入:
// id从1开始自增,上限和id的类型有关,到了上限就无法插入
insert into stu(name, age, sex) values("shen", 10, "man");
insert into stu(name, age, sex) values("zhang", 11, "woman");
insert into stu(name, age, sex) values("shen", 10, "man"),("zhang", 11, "woman");
2. 删除:
delete from stu;
3. 更新
update stu set age=age+1 where name="shen";
面试可能会问上述两种insert的区别:批量导入数据的时候,常用的是一条SQL语句插入多条数据,而不是一条SQL插入一条数据。因为 每条SQL语句都需要C/S之间建立连接,最好是一条SQL插入更多的数据五、查询操作1. 去重select distinct age from stu;
2. union合并查询
select exp1, exp2, ..., expn
from tables [where conditions]
union [all|distinct] -- union默认去重,不用distinct修饰,all表示显示所有重复值
select exp1, exp2, ..., expn
from tables [where conditions]
and用到索引,or被MySQL优化为union也用到了索引3. 带in子查询 select * from stu where age in (11, 22, 33);
select * from stu where age not in (11, 22, 33);
select * from stu where age between 11 and 22; -- [11, 22]
select name from stu where id in (select id from stu where age > 10);
4. 分页查询-- 限制查询的数量,用法:limit count 或 limit start count
select * from stu limit 2; -- 偏移0条开始显示2条,limit 0, 2
select * from stu limit 1, 5; -- 偏移1条开始显示5条
select * from stu limit 5 offset 2; -- 偏移2条开始显示5条
select * from stu order by age desc limit 10,2; -- 先按照年龄降序排列,偏移10条显示2条
面试问题:limit关键字只是对数据的显示进行了过滤,还是说可以影响SQL语句的查询效率?explain:查看SQL语句的执行信息,展示SQL执行的一些关键信息,大致统计一些性能指标,可以查看SQL语句的执行性能创建表的时候关键字unique会创建索引就比如我们注册QQ,登录的时候都会到数据库匹配信息,不可能是注册的早的人匹配信息快登录快,注册的晚的人匹配信息慢登录名。查询一下age由于age没有添加索引,所以数据库引擎做的是整表搜索,效率很低可以通过limit加快查找使用大数据证实limit加快查找建表、插入数据的过程见六这里只使用了100000条数据,如果使用百万、千万级别数据,效果会更明显扫描的数据一旦满足limit条件时,就会停止扫描,可以提高搜索效率实际出现的效率问题:我们若使用如下SQL查询,就会有前几页查询快,后几页查询慢的问题效率主要低在(page_num-1)*n偏移量,偏移需要花费时间select * from t_user limit (page_num-1)*n, n;
我们可以使用id索引直接偏移select * from t_user where id>(page_num-1)*n limit n;
六、创建存储过程procedurecreate table t_user(
id bigint primary key not null auto_increment,
domain char(20) not null,
passwd char(20) not null
);
delimiter $ -- 修改MySQL的分隔符,避免和创建语句的分隔符冲突
create procedure add_t_user(in n int)
begin
declare i int;
set i = 0;
while i < n do
insert into t_user(domain, passwd) values(concat(i+1,"@BugMaker.com"), i+1);
set i = i + 1;
end while;
end$
delimiter ;
call add_t_user(100000);
七、order by排序查询select * from stu where sex= "man" order by age asc;
select * from stu where sex = "woman" order by age desc;
select * from stu where (age between 18 and 24) and sex="man" order by age desc, id desc; -- 先年龄降序,年龄一样则按照id降序
八、group by分组查询一般和聚合函数一起使用select age, count(age) from stu group by age;
select sex, avg(age) from stu group by sex;
select age, sex, count(*) from stu group by age, sex order by age; -- 统计age和sex都相同的有几个人
select age, count(age) from stu group by age having age > 20; -- 条件查询
九、简单笔试题统计表中缴费的总笔数和总金额select count(serno), sum(amount) from bank_bill;
按网点和日期统计每个网点每天的营业额,并按照营业额倒序排序select brno, date, sum(amount) as money from bank_bill group by brno, date order by brno, money desc;
无事小神仙
【面试】Mysql主键索引普通索引索引和唯一索引的区别是什么?
前言在 MySQL 中, 索引是在存储引擎层实现的, 所以并没有统⼀的索引标准, 由于 InnoDB 存储引擎在 MySQL数据库中使⽤最为⼴泛, 下⾯以 InnoDB 为例来分析⼀下其中的索引模型.在 InnoDB 中, 表都是根据主键顺序以索引的形式存放的, InnoDB 使⽤了 B+ 树索引模型,所以数据都是存储在 B+ 树中的。索引用来快速地寻找那些具有特定值的记录,所有MySQL索引都以B-树的形式保存。MySQL提供多种索引类型供选择:普通索引 、唯一性索引、主键索引 、全文索引等等。下面本篇文章就来给大家介绍一下主键索引和普通索引之间的区别 ,希望对你们有所帮助。分析从图中可以看出, 根据叶子节点内容不同,索引类型分为主键索引和非主键索引.主键索引也被称为聚簇索引,叶子节点存放的是整行数据;而非主键索引被称为二级索引,叶子节点存放的是主键的值.如果根据主键查询, 只需要搜索ID这颗B+树而如果通过非主键索引查询, 需要先搜索k索引树, 找到对应的主键, 然后再到ID索引树搜索一次, 这个过程叫做回表.主键索引主键索引不可以为空主键索引可以做外键一张表中只能有一个主键索引普通索引用来加速数据访问速度而建立的索引。多建立在经常出现在查询条件的字段和经常用于排序的字段。被索引的数据列允许包含重复的值唯一索引被索引的数据列不允许包含重复的值总结非主键索引的查询需要多扫描一颗索引树, 效率相对更低.普通索引是最基本的索引类型,没有任何限制,值可以为空,仅加速查询。普通索引是可以重复的,一个表中可以有多个普通索引。主键索引是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值;索引列的所有值都只能出现一次,即必须唯一。简单来说:主键索引是加速查询 + 列值唯一(不可以有null)+ 表中只有一个。
无事小神仙
Oracle解锁表、包、用户、杀会话、停job
一、创建包tzq_server_pkgsys用户以sysdba身份登录Oracle数据库,创建包 tzq_server_pkg ,上代码:CREATE OR REPLACE PACKAGE sys.tzq_server_pkg IS
PROCEDURE unlock_table(table_owner IN VARCHAR2, table_name IN VARCHAR2);
PROCEDURE unlock_package(package_owner IN VARCHAR2,
package_name IN VARCHAR2);
PROCEDURE unlock_user(username IN VARCHAR2);
PROCEDURE stop_job(job_id IN NUMBER);
PROCEDURE kill_session(se_sid IN NUMBER, se_serail# IN NUMBER);
PROCEDURE grant_pris(username IN VARCHAR2);
END tzq_server_pkg;
/
CREATE OR REPLACE PACKAGE body SYS.tzq_server_pkg IS
PROCEDURE unlock_table(table_owner IN VARCHAR2, table_name IN VARCHAR2) IS
CURSOR c1 IS
SELECT DISTINCT '''' || s.sid || ',' || s.serial# || ',@' || s.inst_id || '''' AS si_id
FROM gv$locked_object l
,dba_objects o
,gv$session s
WHERE l.object_id = o.object_id
AND l.session_id = s.sid
AND l.inst_id = s.inst_id
AND o.owner = upper(table_owner)
AND o.object_name = upper(table_name);
c1_rec c1%ROWTYPE;
v_sql VARCHAR2(2000);
BEGIN
FOR c1_rec IN c1 LOOP
v_sql := 'alter system kill session ' || c1_rec.si_id || ' immediate';
dbms_output.put_line(v_sql);
BEGIN
EXECUTE IMMEDIATE v_sql;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLERRM);
END;
END LOOP;
END unlock_table;
PROCEDURE unlock_package(package_owner IN VARCHAR2,
package_name IN VARCHAR2) IS
CURSOR c1 IS
SELECT DISTINCT '''' || a.sid || ',' || a.serial# || ',@' || a.inst_id || '''' AS si_id
FROM gv$session a
,gv$access b
WHERE b.object = upper(package_name)
AND b.owner = upper(package_owner)
AND a.sid = b.sid
AND a.inst_id = b.inst_id;
c1_rec c1%ROWTYPE;
v_sql VARCHAR2(2000);
BEGIN
FOR c1_rec IN c1 LOOP
v_sql := 'alter system kill session ' || c1_rec.si_id || ' immediate';
dbms_output.put_line(v_sql);
BEGIN
EXECUTE IMMEDIATE v_sql;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLERRM);
NULL;
END;
END LOOP;
END unlock_package;
PROCEDURE unlock_user(username IN VARCHAR2) IS
us_name VARCHAR2(200) := username;
v_sql VARCHAR2(2000);
BEGIN
v_sql := 'alter user ' || us_name || ' account unlock';
BEGIN
EXECUTE IMMEDIATE v_sql;
dbms_output.put_line(us_name || '''s account is unlock');
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLERRM);
END;
END unlock_user;
PROCEDURE stop_job(job_id IN NUMBER) IS
CURSOR c1 IS
SELECT DISTINCT '''' || a.sid || ',' || a.serial# || ',@' || a.inst_id || '''' AS si_id
FROM gv$session a
,(SELECT v.sid
,v.id2 job
,v.inst_id inst_id
FROM sys.job$ j
,gv$lock v
WHERE v.type = 'JQ'
AND j.job(+) = v.id2) b
,gv$instance c
WHERE a.inst_id = b.inst_id
AND a.sid = b.sid
AND a.inst_id = c.inst_id
AND c.inst_id = b.inst_id
AND b.job = job_id;
c1_rec c1%ROWTYPE;
v_sql VARCHAR2(2000);
BEGIN
FOR c1_rec IN c1 LOOP
v_sql := 'alter system kill session ' || c1_rec.si_id || ' immediate';
dbms_output.put_line(v_sql);
BEGIN
EXECUTE IMMEDIATE v_sql;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLERRM);
NULL;
END;
END LOOP;
END stop_job;
PROCEDURE kill_session(se_sid IN NUMBER, se_serail# IN NUMBER) IS
p_sid NUMBER := se_sid;
p_serail NUMBER := se_serail#;
is_back_process NUMBER := 0;
CURSOR c1 IS
SELECT DISTINCT '''' || a.sid || ',' || a.serial# || ',@' || a.inst_id || '''' AS si_id
FROM gv$session a
WHERE a.sid = p_sid
AND a.serial# = p_serail;
c1_rec c1%ROWTYPE;
v_sql VARCHAR2(2000);
BEGIN
IF se_sid IS NULL OR se_serail# IS NULL THEN
dbms_output.put_line('sid is null or serail# is null');
RETURN;
END IF;
BEGIN
SELECT 1
INTO is_back_process
FROM gv$session
WHERE sid = se_sid
AND serial# = se_serail#
AND TYPE = 'BACKGROUND';
EXCEPTION
WHEN OTHERS THEN
is_back_process := 0;
END;
IF is_back_process = 1 THEN
RETURN;
END IF;
FOR c1_rec IN c1 LOOP
v_sql := 'alter system kill session ' || c1_rec.si_id || ' immediate';
dbms_output.put_line(v_sql);
BEGIN
EXECUTE IMMEDIATE v_sql;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLERRM);
NULL;
END;
END LOOP;
END kill_session;
PROCEDURE grant_pris(username IN VARCHAR2) IS
us_name VARCHAR2(200) := username;
v_sql VARCHAR2(2000);
v_sql2 VARCHAR2(2000);
v_sql3 VARCHAR2(2000);
v_sql4 VARCHAR2(2000);
v_sql5 VARCHAR2(2000);
v_sql6 VARCHAR2(2000);
v_sql7 VARCHAR2(2000);
BEGIN
v_sql := 'grant create synonym,create table,create type,create sequence,create view ,create materialized view,create job,create database link,connect,resource,create procedure ,debug any procedure, debug connect session to ' ||
us_name;
v_sql2 := 'grant select on gv_$locked_object to ' || us_name;
v_sql3 := 'grant select on dba_objects to ' || us_name;
v_sql4 := 'grant select on gv_$session to ' || us_name;
v_sql5 := 'grant select on gv_$process to ' || us_name;
v_sql6 := 'grant select on gv_$sql to ' || us_name;
v_sql7 := 'grant select on gv_$access to ' || us_name;
BEGIN
EXECUTE IMMEDIATE v_sql;
EXECUTE IMMEDIATE v_sql2;
EXECUTE IMMEDIATE v_sql3;
EXECUTE IMMEDIATE v_sql4;
EXECUTE IMMEDIATE v_sql5;
EXECUTE IMMEDIATE v_sql6;
EXECUTE IMMEDIATE v_sql7;
dbms_output.put_line('grant success!');
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLERRM);
END;
END grant_pris;
END tzq_server_pkg;
/
二、授权给需要使用的用户logsys用户以sysdba身份登录Oracle数据库,给需要使用该包(sys.tzq_server_pkg)的用户授予 的权限,执行下面命令授权:grant execute on sys.tzq_server_pkg to log;
三、解锁表:执行存过unlock_table(schema_name, table_name)以上面被授权的log用户,打开命令行窗口,执行下列SQL:set serveroutput on
execute sys.tzq_server_pkg.unlock_table('LOG','tzq_log_t');
四、解锁包:执行存过unlock_package(schema_name, pkg_name)以上面被授权的log用户,打开命令行窗口,执行下列SQL:set serveroutput on
execute sys.tzq_server_pkg.unlock_package('LOG','tzq_log_pkg');
五、解锁用户:执行存过unlock_user(username)以上面被授权的log用户,打开命令行窗口,执行下列SQL:set serveroutput on
execute sys.tzq_server_pkg.unlock_user('LOG');六、停止job任务:执行存过stop_job(job_id)以上面被授权的log用户,打开命令行窗口,执行下列SQL:set serveroutput on
execute sys.tzq_server_pkg.stop_job(6);七、杀session会话:执行存过kill_session(se_sid, se_serail#)7.1、查询需要kill的session的SID及serial#执行下列SQL:SELECT * FROM gv$session;找到你需要kill的那个session会话,拿到SID及serial#:159, 37297.2、执行存过kill_session(se_sid, se_serail#)执行存过kill_session(),kill掉上面的那个session会话。在命令行执行下面的SQL:set serveroutput on
execute sys.tzq_server_pkg.kill_session(159, 3729);
八、给新建的用户授权:执行存过 grant_pris(username)以上面被授权的log用户,打开命令行窗口,执行下列SQL:set serveroutput on
execute sys.tzq_server_pkg.grant_pris('log');
无事小神仙
MySQL Server 层四个日志
一、MySQL Server层日志简介一个mysql client发起一个连接请求,处理请求的过程如下图所示:MySQL日志是在MySQL server上生成的,不管更改哪个存储引擎,这些日志都是需要有的,包括:错误日志:记录mysqld服务运行过程中出现的coredump、error、exception等查询日志:记录MySQL Server收到的所有增删改查SQL。由于上线项目的SQL太多了,开启查询日志IO太多导致MySQL效率低下,我们一般都不会开启查询日志,只有调试时才开启二进制日志:记录数据的更改(insert、update、delete、alter …),非常重要,可用于数据恢复,主从复制。主从复制技术依赖于log-bin,主库所有的更改操作都记录在log-bin里,从库从log-bin读取主库所有的操作,自己再执行一遍。慢查询日志:记录了一些执行时间超过指定值的SQL语句,可供开发人员分析耗时SQL,从而针对性优化查看日志相关变量mysql> show variables like 'log%';
+----------------------------------------+----------------------------------------+
| Variable_name | Value |
+----------------------------------------+----------------------------------------+
| log_bin | ON | -- 是否开启二进制日志
| log_bin_basename | /var/lib/mysql/binlog | -- 二进制日志存储位置
| log_bin_index | /var/lib/mysql/binlog.index |
| log_bin_trust_function_creators | OFF |
| log_bin_use_v1_row_events | OFF |
| log_error | /var/log/mysql/error.log | -- 错误日志存储位置
| log_error_services | log_filter_internal; log_sink_internal |
| log_error_suppression_list | |
| log_error_verbosity | 2 |
| log_output | FILE |
| log_queries_not_using_indexes | OFF |
| log_raw | OFF |
| log_replica_updates | ON |
| log_slave_updates | ON |
| log_slow_admin_statements | OFF |
| log_slow_extra | OFF |
| log_slow_replica_statements | OFF |
| log_slow_slave_statements | OFF |
| log_statements_unsafe_for_binlog | ON |
| log_throttle_queries_not_using_indexes | 0 |
| log_timestamps | UTC |
+----------------------------------------+----------------------------------------+
二、配置文件参数my.cnf通过set方法只能影响当前session,session关闭后设置失效,如果想要配置永久有效,需要在配置文件上进行设置,然后重启MySQL服务,就可以永久生效linux下重启mysqld服务的命令:sudo service mysqld restart我们查看一下配置文件/etc/mysql/my.cnf给出log-error的路径就是开启了log-error,如果不自定义log-error的路径,默认在data_dir在开启log-bin=mysql-bin的同时还要加上server-id=1(表示当前MySQL Server的身份),否则sudo service mysqld restart无法重启服务设置过期的时间expire_log_days,因为总有一天磁盘会被这个日志占满,导致服务器不可运行,超过设置时间后日志文件会被删除三、错误日志错误日志是 MySQL 中最重要的日志之一,它记录了mysqld 启动和停止,以及服务器在运行过程中发生任何严重错误(coredump,error,exception…)时的相关信息。当数据库出现故障导致无法正常使用时,可以首先查看此日志mysqld 使用的错误日志名为 host_name.err(host_name 为主机名) ,并默认在参数data_dir(数据目录)指定的目录中写入日志文件四、查询日志查询日志记录了client发送的所有SQL语句由于上线项目sql特别多,开启查询日志IO太多导致MySQL效率低,我们一般都不会开启,只有在调试时才开启,比如通过查看sql发现热点数据从而可以进行缓存show global variables like '%genera%';
打开全局变量general_log开关:五、二进制日志不是明文,不能直接查看,需要通过mysqlbinlog工具(mysql原生自带)解析binlog日志文件二进制日志(BINLOG)记录了所有的 DDL(数据定义语言)语句和 DML(数据操纵语言) 语句,但是不包括数据查询语句(不记录select操作,记录的是数据库的更改操作)语句以“事件”的形式保存,它描述了数据的更改过程。二进制日志对于灾难时的数据恢复起着极其重要的作用。两个重要的应用场景:主从复制、数据恢复主从复制:主库所有的更新操作(update、delete、insert、alter …)都记录在binlog中,从库读主库的binlog,把binlog的所有操作在从库上在进行一遍查看当前的binlog:show binary logs; -- show master logs;
binlog默认在MySQL的配置文件/etc/mysql/my.cnf配置的data_dir下1. 演示binlog记录更改我们先刷新一下,生成一个新的binlog切换数据库更改一下数据再次查看binlog我们发现日志的filesize从154字节—>710字节,肯定记录我们刚才的数据更改操作如果我们直接cat日志查看,会发现不是明文,无法直接查看我们需要通过mysqlbinlog进行查看,如下:mysqlbinlog --no-defaults --database=school --base64-output=decode-rows -v --start-datetime='2022-03-01 00:00:00' --stop-datetime='2022-03-31 00:00:00' mysql-bin.000003 | more
database:指定查看某个库的更改base64-output:binlog解码方式start-datetime & stop-datetime:指定查看某个时间段内的更改,不写则查看所有的更改mysql-bin.000003:查看的二进制日志文件路径我们查看一下binlog@1、@2、@3、@4:表示数据库表的4个字段server id:表示我们在my.cnf中设置的id,用于标识当前MySQL的身份at 565、at 621:指的是当前事件在binlog记录的位置,数据恢复的时候使用2. 演示binlog数据恢复现在创建数据库mytest,并创建表,添加数据假如现在有人把库删除了:这时mytest库的所有表和数据都没有了,然而这些操作都会记录在二进制日志binlog里面理论上来说,可以从binlog把丢失的数据恢复出来。由于恢复过程也是对数据的修改,所以恢复过程产生的日志也要记录在binlog中,这就需要我们指定binlog恢复区间我们现在知道,我们建库、建表、插入数据的操作都记录在mysql-bin.00003文件中我们现在刷新一下,生成一个新的binlog,这就可以让我们接下来数据恢复的操作被记录在mysql-bin.00004文件中,而不会在追加到mysql-bin.00003我们先查看mysql-bin.00003,找需要恢复的区间从mysql-bin.000003中拿出区间内所有的操作,通过管道放到MySQL shell上执行mysqlbinlog --start-position=775 --stop-position=1410 binlog.000003 | mysql -uroot -p
start-position和stop-position表示左闭右开区间:[start-position, stop-position)查看一下当前的库再查看一下表和数据到这里,数据已经全部恢复了我们不仅可以通过binlog记录的位置,得到需要恢复的区间,也可以通过binlog记录的时间得到需要恢复的区间mysqlbinlog --start-datetime='2021-05-06 04:34:32' --stop-datetime='2022-04-24 04:36:02' binlog.000003 | mysql -uroot -p
参数为:start-datetime、stop-datetime,也是左闭右开区间由于binlog有一个过期时间,过期的日志数据都会备份到磁盘上的.sql脚本文件中,没有过期的数据可以直接通过binlog恢复,如果需要恢复过期的数据,通过以下命令即可:mysql> source ~/data.sql
$cat ~/data.sql | mysql -u root -p
六、慢查询日志MySQL可以设置慢查询日志,当SQL执行的时间超过我们设定的时间,那么这些SQL就会被记录在慢查询日志当中我们通过查看日志,用explain分析这些SQL的执行计划,来判定为什么效率低下,是没有使用到索引?还是索引本身创建的有问题?或者是索引使用到了,但是由于表的数据量太大,花费的时间就是很长,那么此时我们可以把表分成n个小表,比如订单表按年份分成多个小表等慢查询日志相关的参数如下所示:慢查询日志记录了包含所有执行时间超过参数 long_query_time(单位:秒)所设置值的 SQL语句的日志,在MySQL上用命令可以查看,如下:这个值是可以修改的:现在修改成执行时间超过1秒的SQL都会被记录在慢查询日志当中!可以设置为0.01秒,表示10毫秒慢查询日志,默认名称是host_name-slow.log,存放在MySQL的配置文件/etc/mysql/my.cnf配置的data_dir下,内容格式显示大致如下:show profiles命令可有查看sql详细的运行时间,全局变量的名字是:profiling首先需要:set profiling=on
无事小神仙
好好学习一下InnoDB中的页
一. 前言周末没啥计划,把大佬的<MySQL是怎样运行的>又翻了出来,重新学习一下页的概念。页这个东西,看起来不怎么显眼,但是深层的东西都会碰到他,又爱又恨,逼着人必须弄懂。二. 从宏观层面看页高并发里面有一种提高性能的思路是 :通过批处理一次性处理大量数据,避免频繁的网络流量和IO。MySQL 的页就是基于这种概念,磁盘是存放数据的载体,而数据处理会发生了内存中,所以流程大致分为:S1 : 首先对数据进行切分,划分成若干页S2 : 每次读取的时候,都直接把一整页读取到内存中S3 : 外部读取的时候,直接对内存中的数据进行读取和操作S4 : 如果发生了修改操作,需要把内存的内容刷新到磁盘上页的好处这里比较模糊的是为什么要衍生出一个页,而不是通过行级别进行处理。首先解决的就是 IO 问题,当然如果说每个页只读一条,那么这种就不算优势,但是我们大批量读取的时候,往往是查询连续的数据 , 相对而言取舍后,效率就更高了。避免碎片化,行的级别太低了,大小也不同,使用行的时候,存储空间不便于分配提高并发和锁,可以通过控制事务到一个页里面,减少事务的粒度提高维护性和通用性 ,当发生重整时,页的处理会更简单三. 页的基本内容页的概念与索引关联的概中主要包括 :页 (Page): 页是数据存储的基本单位,是一个固定大小的数据块,通常是16K行 (Row): 行是数据库中的基本数据单位 ,代表表中的一个记录分组(Group):将一个页里面除了删除的记录进行逻辑划分,取每组最后一条记录作为偏移量标志位槽(Slot):每个分组的最后一条数据会在页目录里面作为一个指针存在,这个指针就是一个槽页目录 (Page Directory):用来管理数据页的一种数据结构,目录内记录了指针,索引等位置信息3.1 页的数据结构File Header 和 Page Header 包含了该页的基础属性和状态信息等Infimum / Supremum 是虚拟的行记录,用于限定记录的边界,他们都是虚拟的,不表示任何存在Infimum 标识比该页任何值都要小的值Supremum 标识比该页任何值都要大的值用户记录和空闲记录是实际的存储空间,随着插入数据空闲空间会越来越小页面目录用来存储记录的相对位置,通过稀疏目录的方式加快了查询的数据File Trailer 目的是为了保证数据的完整性,其中会存放一个校验和保证数据是正确的插入数据带来的结构变化3.2 用户空间内的数据行结构其中主要的参数是 :n_owned:当前记录拥有的记录数 ,通过该数据来确定每组数据的大小heap_no:当前记录在堆的位置,最小和最大的heap_no 分别是 0,1 ,标识在最上面next_record:下一记录的相对位置 , 用于保证数据成一个链表结构3.3 页目录我们或多或少都接触过数组或者集合,对于数组的查询方式有很多,正序或者逆序,或者效率更高的二分法前提 : MySQL 的数据按照行记录进行存储,在一个表中,行的数据是有序的目录 :但是不论多么优良的算法,在大数据量的场景下,还是会有很高的性能损耗,而 MySQL 为了解决这种场景,采取的是目录的方式。 目录中通过槽和分组,得到了一个数据的精简模型,通过精简的数据快速查询对应的分组,再在分组里面进行循环查找槽和分组有个资料里面说的是一个数据行就对应一个槽,也有说多个记录一个槽,我这里倾向于后一种说法,即稀疏目录。页目录存放了记录的相对位置,每个相对位置即为一个槽,在InnoDB 里面是使用稀疏目录 (sparse directory), 即一个槽会属于多个记录 (4-8条)最小记录的条数是1最大记录所在分组的记录数是1-8其他分组在 4-8之间指向原理 查询数据时,首先通过二分法在页目录中进行查询 当查询到分组范围后,再通过分组里面的 next_record 查询具体的数据四. 问题集4.1 索引 和 数据页 有什么区别两者不是同一个东西,存储的数据和结构都不同在索引中,每一个 B+树节点对应一个索引页,一个索引页中存储索引键值和指向指针数据查询时,通过根索引页开始,遍历索引树,从而拿到指向数据行的指针InnoDB 会通过索引中的数据行指针定位到数据页 (直接通过物理地址指向槽号)除了这些页,InnoDB 中还有存放表空间头部信息的页,Buffer 页等。4.2 页的大小是什么决定的页的大小是由创建数据库表时指定的存储参数 innodb_page_size 决定的参数一旦设置就不能更改,不然就得刷页里面大量的数据CREATE TABLE my_table (...) ENGINE = InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8 PAGE_COMPRESSED=1 PAGE_SIZE=64K;4.3 页的大小对哪些情况有影响索引效率 :前面说了,索引过程中会通过每页的最大最小进行快速匹配,而较大的页一定程度上会使相同数据量情况下拥有更少的页,从而降低索引节点的数量,索引树高度也因此降低。查询效率会有所提高内存占用 :较大的页会在内存中占用更多的空间。因为读取时,每次都是读取一整页,所以内存每次读取得更多。其他硬件影响 :更大的页会影响磁盘IO和CPU,IOPS 方面都会带来更多的压力总结 :提高效率,但是增加了系统负载。4.4 一般情况下说的链表有哪几个一个列里面的数据行之间通过 next_record 形成的单向链表上文说到了每个数据行上面会有个 next_record 参数,该参数记录了真实数据达到下一条记录的真实数据的偏移量,这里有几点值得注意 :这里的顺序不是插入数据,而是主键值由小到大的顺序上一条指向的是下一条的value的位置,而不是 Header 头的位置不同数据页之间组成的双向链表上面的结构图看过了,每个页里面都会包含 File Header 和 Page Header 两个对象。Page Header : 记录当前页的状态信息和规则,例如槽数,记录数,剩余空间数等等File Header : 记录当前页的标准信息,包括页的编号,页所在的表空间,上一页页号和下一页页号而双向成方式不言而喻,都知道上页 (FIL_PAGE_PREV) 和 下页(FIL_PAGE_NEXT)的页号了,那访问完全没问题了 , 由于都只存了上一个和下一个,也就形成了标准的链表结构。补充 : 上面看到的这种通常是指 LRU 链表,还有一个双向链表是 Flush List (刷新链表),这个链表是在数据页发生修改后,使用刷新链表可以让数据按照一定的顺序刷新到磁盘上4.5 如果页的空间满了怎么办首先,页的的大小是在存储引擎创建的时候就确定了,所以空间固定。其次页内数据是按照主键进行排序,所以这个时候插入铁定空间超了在这种场景下,会触发页分裂 ,此时 InnoDB 会执行下列操作 :S1 : 创建新的数据页S2 : 按照排序方式将部分数据迁移到新页S3 : 更新上下页关系和对应的索引关系这里由于页是双向链表进行的关联,所以插入并不会对数据结构进行大的破坏,只需要对应的上下页进行更新就行了。4.6 如果页的空间空了怎么办既然会有页分裂,那就有可能会出现分裂的页不均衡的情况,长时间下去,就会形成很多空闲块,这样的结构也是不合理的,不仅会占用不必要的空间,还会导致查询性能降低。为了避免这些问题,InnoDB 会有页合并的功能 , 原理和上面的类型。相邻页尝试合并,然后重新更新引用和索引。4.7 删除的数据何时被清理之前看到了数据被删除后,其目录数据里面的 delete_mask 会被置为已删除。此时的数据处在逻辑删除的状态,通过上面说的 next_record (下一记录的相对位置)指向后续存在的正常数据。这样做的目的主要是避免碎片,提高删除的性能(只需要修改标识和引用),同时保证了删除的事务。但是长此以往就会有大量的删除数据占用空间,为了避免这种情况,InnoDB 会定期的进行清理,同时重新整理数据页。4.8 数据页和B+树及索引的关系数据页是为了存储数据行的,存放的是二进制数据,通常数据行按照主键的顺序存放B+树是一种数据结构,也是索引的结构,B+树结构让索引更加有效和便于管理索引中的B+树叶子节点存储了索引条目,每个条目对应一个数据行的物理指针(通常是数据行的槽号) 当获得槽号后,就直接通过槽号读取想要的数据,并且返回页和索引是相辅相成的,如果没有索引,页就需要在单向链表里面向下寻找,直到找到对应的数据总结页是存储的基础,也是索引的基础,了解了页后面就可以深入的了解索引了。这一块没了解太深,毕竟这东西其实我应用的场景几乎没有,主要是不弄清楚后面读起来很难受。尽量做到了自己去输出东西,整理了一些问题,但是毕竟站在别人修好的路上面,有些东西不能保证一定是对的,也有可能是我理解有误,如果有问题建议去看原文或者官方文档。附录头部信息对于我们日常业务中几乎是没太大用的,这里只记录几个我认为和上文有一定关联的参数 :页头部信息 PAGE_N_DIR_SLOTS : 页目录中的槽数量 PAGE_N_HEAP : 本页中的记录数量 PAGE_GARBAGE : 已删除记录中的字节数 PAGE_LAST_INSERT :最后插入记录的位置 PAGE_DIRECTION :记录插入的方向 PAGE_N_RECS : 该页中记录的数量 PAGE_LEVEL : 当前页在 B+树中所处的层级 PAGE_INDEX_ID : 索引ID文件头部信息 FIL_PAGE_OFFSET : 页号 FIL_PAGE_PREV : 上一个页的页号 FIL_PAGE_NEXT : 下一个页的页号 FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID : 页属于哪个表空间参考文档小册 : MySQL是怎样运行的MySQL 技术内幕
无事小神仙
MySQL存储引擎以及索引
一、数据库引擎1. 查看数据库引擎mysql> show engines;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine | Support | Comment | Transactions | XA | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| ARCHIVE | YES | Archive storage engine | NO | NO | NO |
| BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO |
| MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO |
| FEDERATED | NO | Federated MySQL storage engine | NULL | NULL | NULL |
| MyISAM | YES | MyISAM storage engine | NO | NO | NO |
| PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO |
| InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES | YES | YES |
| MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO |
| CSV | YES | CSV storage engine | NO | NO | NO |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
2. 查看表结构show create table [student]:查看表结构,其中表使用的数据库引擎和字符集等可以在配置文件中修改。windows下的配置文件为安装目录下的my.ini,linux则在/etc/mysql/my.cnf3. 查看表相关文件表相关文件目录:/var/lib/mysql使用MyISAM存储引擎的表对应的文件有三个:*.frm,*.MYD,*.MYI,分别表示表结构、表数据、表索引使用InnoDB存储引擎的表对应的文件有两个:*.frm,*.ibd,分别表示表结构、表数据和表索引,数据和索引放在一个文件中面试问题:为什么使用InnoDB存储引擎的表会自动生成主键,而使用MyISAM存储引擎的表不会自动生成主键?因为MyISAM的数据和索引是单独存放的,手动加上主键会生成主键索引存放在*.MYI,没有主键的话*.MYI里就不用存放索引。而InnoDB会默认生成一个整型类型的索引,因为Innodb的数据和索引放在一个文件中,数据就是放在索引树上的,没有索引,数据也没有地方存放。4. 各存储引擎的区别种类锁机制B树索引哈希索引外键事务索引缓存数据缓存MyISAM表锁支持不支持不支持不支持支持不支持InnoDB行锁支持不支持支持支持支持支持Memory表锁支持支持不支持不支持支持支持锁机制:表示数据库在并发请求访问的时候,多个事务在操作时,并发操作的力度,能用行锁解决,就别用表锁,锁粒度大了降低并发度B树索引和哈希索引:主要是加速SQL的查询速度外键:子表的字段依赖父表的主键,设置两张表的依赖关系事务:多个SQL语句,保证它们共同执行的原子操作,要么成功要么失败,不能只成功一部分,失败需要回滚事务索引缓存和数据缓存:和MySQL Server的查询缓存相关,索引是存放在磁盘上的,在没有对数据和索引做修改之前,重复查询可以不用进行磁盘I/O(数据库的性能提升,目的是减少磁盘I/O提升访问效率),读取上一次内存中查询的缓存就可以了二、MySQL索引当表中的数据量达到上百万的时候,SQL查询花费的时间会很长,需要使用索引加速SQL查询由于 索引也是需要存储成索引文件的,因此使用索引也会涉及磁盘I/O操作。如果索引过多,使用不当,SQL查询时会造成大量无用的磁盘I/O操作,降低查询效率。此外,我们 改动数据以后,不仅是数据文件需要做修改,索引文件也需要修改,索引过多,修改的索引也会更多,所以索引并不是越多越好。1. 索引分类索引是创建在表上的,是对数据库表中一列或多列的值进行排序的一种结果,其核心就是提高查询的速度物理上分为:聚集索引、非聚集索引逻辑上分为: 普通索引:没有任何限制条件,可以给任何字段创建普通索引(一张表的一次SQL查询只能使用一个索引,比如where age=1 and sex="man"只能使用一个索引) 唯一性索引:使用unique修饰的字段,值不能重复,主键索引就是一种唯一性索引 主键索引:使用PRIMARY KEY修饰的索引,主键字段会自动创建索引 单列索引:在一个字段上创建索引 多列索引:在表的多个字段上创建索引 (uid+cid,age+name等,先按第一个字段排序,再按第二个字段排序),多列索引必须使用到第一个列,才能用到多列索引,否则索引用不上,比如对于uid+cid的索引,我们这样where cid=2是无法使用该索引的 全文索引:使用FULLTEXT参数可以设置全文索引,只支持CHAR,VARCHAR和TEXT类型的字段上,常用于数据量较大的字符串类型上(真实项目中使用的较少,比如我们使用百度,肯定不会在数据库搜索文本,而是使用ElasticSearch等搜索引擎加速搜索)索引的优点:提高查询效率索引的缺点:索引并不是越多越好,过多的索引会导致CPU使用率居高不下,数据的改变也会造成索引文件的改变,过多的磁盘I/O造成CPU负载太重2. 索引的创建和删除创建表的时候指定索引字段:CREATE TABLE student(id INT,
name VARCHAR(50),
sex ENUM('male', 'female'),
INDEX(id));
在已经创建的表上添加索引:CREATE [UNIQUE] INDEX 索引名 ON 表名(属性名(length) [ASC | DESC]);
length表示用该字段的前length个字符建索引,如果字段很长,索引文件就会很大,搜索也慢,如果前length个字符可以区分该字段,就可以使用前length个字符建立索引删除索引:DROP INDEX 索引名 ON 表名;
此时表结构如下:show create table student \G
使用具有主键索引的id进行过滤查找:explain select * from student where uid=3;
从索引树上直接获取数据,并没有整表扫描,对于当前uid创建了索引,无论查询uid是多少的信息,都能在索引树上取得,直接命中使用没有索引的name属性扫描explain select * from student where name="zhangsan";
就算zhangsan排在第一个,也需要整表扫描,不扫描完,不知道其他行有没有zhangsan给name添加索引create index nameidx on student(name);
用name索引加速搜索type为ref,表示在扫描索引树;若type为const,表示使用主键索引或唯一性索引,直接精确匹配key_len这里是152,对于给字符串类型数据建立索引的时候,一般会限制索引长度。若前面一部分字符区可以用于区分不同的数据,没必要使用很长长度的索引(key_len很大)。因为索引长了,索引文件会变大,就会使用更多的磁盘IO,应尽量避免然而添加索引后,不一定就能使用到索引,因为MySQL server有优化,它会先进行分析,如果发现使用索引需要扫描的数据基本上是所有数据的大概百分之七八十左右,其实是不会使用索引的,因为如果花费差不多,读索引文件花费磁盘I/O,还要扫描索引树,还不如直接整张表搜索取数据3. 关于缓存问题对于相同的操作,若中间没有更新数据(insert/delete/update),则第一次花费时间长,第二次花费时间短,这是因为存储引擎对索引和数据进行了缓存。 第一次查询后的结果会放在数据缓存或者索引缓存里,第二次就不用花费磁盘I/O从磁盘读取索引了。4. 过滤条件字段涉及类型转换则无法使用索引查看表结构后发现,password属性是varchar,然而查询的时候使用的是int,这就涉及到了类型转换,所以不会使用索引。此外,如果用到了mysql的聚集函数或表达式计算,也不会用到索引5. 删除索引drop index pwdidx on t_user;
6. explain字段含义select_typetabletyperefExtra7. 加索引优化原则若经常作为过滤条件(where)的属性,需要加上索引给字符串属性添加索引的时候,最好根据字段前面一部分字符创建索引即可,即限制索引的长度(key_len)where过滤条件使用的索引字段涉及类型强转、mysql聚合函数调用、表达式计算等,则不会使用索引。
无事小神仙
MySQL 回滚日志 undo log
一、引入 undo log一般数据库引擎默认工作在事务的中间两个隔离级别:TRANSACTION_READ_COMMITTED,已提交读,oracle默认工作级别。不允许读取未commit的数据,这个级别仍然允许不可重复读和虚读产生。TRANSACTION_REPEATABLE_READ,可重复读,MySQL默认工作级别。保证事务再次读取是依然得到相同的数据,部分解决了虚读,但虚读是仍然会出现的注:事务隔离级别越高,为避免冲突所花费的性能也就越多在可重复读级别,实际上可以解决部分的虚读问题,但是不能防止update更新产生的虚读问题,要禁止虚读产生,还是需要设置串行化隔离级别InnoDB的已提交读和可重复读的底层实现原理:MVCC(多版本并发控制),MVCC提供了一种并发的读取方式,即快照读 ,同一份数据会有多个版本InnoDB提供了2种读取操作:锁定读和非锁定读锁定读就是读取的时候加锁(S或X)非锁定读就是读取的时候没有加锁,指的就是MVCC提供的快照读—>快照读依赖的是底层的undo log回滚日志ACD特性用事务日志实现,I 特性用锁+MVCC 实现。事务日志分为undo log(回滚日志) 和 redo log(重做日志)二、undo log1. undo log的概念undo log和redo log统称事务日志,不同于binlog是MySQL Server层的日志,这两个属于存储引擎层的日志redo log:重做日志,记录事务操作的变化,确保事务的持久性。redo log在事务开始时就开始记录,不管事务是否commit都会记录。出现异常时(如数据持久化过程断电),重启后InnoDB会使用redo log恢复到断电前的状态,保证数据的完整性undo log:回滚日志,保存了事务发生之前的数据的一个版本,用于事务的回滚操作,同时也是实现多版本并发控制(MVCC)下读操作(快照读)的关键技术2. undo log的作用undo log回滚日志的主要作用:事务发生错误时回滚rollback,数据更新之前,会把原始数据保存在回滚日志中,保证事务出错回滚或者我们手动回滚的时候,能够在回滚日志中找到最初的数据提供了MVCC的非锁定读(快照读),依赖undo log实现3. undo log的数据结构在MVCC下,针对表的所有记录,除了我们自行设定的字段book_id、book_name、auther还会添加几个字段DB_ROW_ID:和MVCC的关系不大,这个是我们创建表的时候,如果没有加PRIMARY KEY,那么InnoDB就自动生成主键列id(DB_ROW_ID),通过这个id作为主键创建索引树,在B+树的叶子节点上存放数据。由于InnoDB的数据和索引是存放在一起的,如果我们没有设置主键,InnoDB会自动生成主键DB_TRX_ID:事务ID,我们每打开一个客户端session,输入begin,向服务器请求开启一个事务。事务开启请求发到MySQL server上,MySQL server为每个事务都会分配一个全局的,不冲突的事务ID(InnoDB存储引擎分配的,因为它才支持事务)。当某个事务修改数据时,DB_TRX_ID放的就是该事务的ID,同一个事务无论怎么更改数据,这个事务ID都不会改变DB_ROLL_PTR:回滚指针,我们看到,存放的是地址,这个地址表示的是一个数据的内存的位置,看起来是一个链表,上图中的这个橙色表是最后的成品,那么这个表是怎么变来的呢?最初的时候表是这样的:现在有一个ID为1001的事务来更改这条数据,这个事务把book_name改成了“笑傲江湖(吕颂贤)”此时数据涉及到修改了,修改的数据存放在当前这个表中,那修改之前的数据怎么办?防止事务回滚恢复修改前的状态,需要将最初的数据存放在undo log中!现在undo log的数据结构如下:现在有一个ID为1002的事务来更改这条数据,这个事务把book_name改成了“笑傲江湖(李亚鹏)”现在undo log的数据结构如下:MVCC会给我们创建的表增加2个列,一个是事务ID,一个是指向修改前的数据的指针,修改之前的数据都是放在undo log回滚日志当中。对于每个版本的数据,是哪个事务改的DB_TRX_ID就写哪个事务ID,DB_ROLL_PTR指针把当前数据和旧数据串成1个链表。从当前行的DB_ROLL_PTR可以访问到旧数据,进行回滚就很简单了三、undo log举例原始的user表如下:MVCC机制会对这张表增加2列,修改当前数据的事务ID(DB_TRX_ID)和指向undo log的指针(DB_ROLL_PTR)我们把id=7的age改为16,如下图所示:原来(id=7,age=15)这个版本的数据放到了undo log中这是我们再增加了1行新数据,由于新增的数据没有对应的老版本,所以undo log中对应为NULL,如下图所示:回滚的时候发现是NULL,就知道是insert增加的数据,直接执行insert的反操作delete就可以了同样的,如果我们这时要修改刚刚insert的数据,将id=23的age改成23在已提交读和可重复读隔离级别下,当我们去读数据的时候,可不是像串行化一样通过SX锁或间隙锁实现,而是纯粹通过MVCC的快照读实现的。
无事小神仙
MySQL体系架构
背景很多小伙伴工作很长时间了,对于MySQL的掌握程度却仅仅停留在表面的CRUD,对于MySQL深层次的原理和技术知识了解的少之又少,随着工作年限的不断增长,职场竞争力却是不断降低的。很多时候,出去面试时,被面试官吊打的现象成了家常便饭。专栏持续更新中:MySQL详解一、MySQL体系架构我们先来看看MySQL的体系架构图,如下所示。从MySQL的架构图,我们可以看出MySQL的架构自顶向下大致可以分为网络连接层、数据库服务层、存储引擎层和系统文件层四大部分。接下来,我们就来简单说说每个部分的组成信息。二、网络连接层网络连接层位于整个MySQL体系架构的最上层,主要担任客户端连接器的角色。提供与MySQL服务器建立连接的能力,几乎支持所有主流的服务端语言,例如:Java、C、C++、Python等,各语言都是通过各自的API接口与MySQL建立连接。三、数据库服务层数据库服务层是整个数据库服务器的核心,主要包括了系统管理和控制工具、连接池、SQL接口、解析器、查询优化器和缓存等部分。3.1 连接池主要负责存储和管理客户端与数据库的连接信息,连接池里的一个线程负责管理一个客户端到数据库的连接信息。3.2 系统管理和控制工具提供数据库系统的管理和控制功能,例如对数据库中的数据进行备份和恢复,保证整个数据库的安全性,提供安全管理,对整个数据库的集群进行协调和管理等。3.3 SQL接口主要负责接收客户端发送过来的各种SQL命令,并将SQL命令发送到其他部分,并接收其他部分返回的结果数据,将结果数据返回给客户端。3.4 解析树主要负责对请求的SQL解析成一棵“解析树”,然后根据MySQL中的一些规则对“解析树”做进一步的语法验证,确认其是否合法。3.5 查询优化器在MySQL中,如果“解析树”通过了解析器的语法检查,此时就会由优化器将其转化为执行计划,然后与存储引擎进行交互,通过存储引擎与底层的数据文件进行交互。3.6 缓存MySQL的缓存是由一系列的小缓存组成的。例如:MySQL的表缓存,记录缓存,MySQL中的权限缓存,引擎缓存等。MySQL中的缓存能够提高数据的查询性能,如果查询的结果能够命中缓存,则MySQL会直接返回缓存中的结果信息。四、存储引擎层MySQL中的存储引擎层主要负责数据的写入和读取,与底层的文件进行交互。值得一提的是,MySQL中的存储引擎是插件式的,服务器中的查询执行引擎通过相关的接口与存储引擎进行通信,同时,接口屏蔽了不同存储引擎之间的差异。MySQL中,最常用的存储引擎就是InnoDB和MyISAM。InnoDB和MyISAM存储引擎需要小伙伴们重点掌握,高频面试考点,也是成为架构师必知必会的内容。五、系统文件层系统文件层主要包括MySQL中存储数据的底层文件,与上层的存储引擎进行交互,是文件的物理存储层。其存储的文件主要有:日志文件、数据文件、配置文件、MySQL的进行pid文件和socket文件等。5.1 日志文件MySQL中的日志主要包括:错误日志、通用查询日志、二进制日志、慢查询日志等。错误日志主要存储的是MySQL运行过程中产生的错误信息。可以使用下面的SQL语句来查看MySQL中的错误日志。show variables like '%log_error%';
通用查询日志主要记录MySQL运行过程中的一般查询信息,可以使用下面的SQL语句来查看MySQL中的通用查询日志文件。how variables like '%general%';
二进制日志主要记录对MySQL数据库执行的插入、修改和删除操作,并且也会记录SQL语句执行的时间、执行的时长,但是二进制日志不记录select、show等不修改数据库的SQL。主要用于恢复数据库的数据和实现MySQL主从复制。查看二进制日志是否开启。show variables like '%log_bin%';
1
查看二进制日志的参数show variables like '%binlog%'
查看日志文件show binary logs;
慢查询日志慢查询主要记录的是执行时间超过指定时间的SQL语句,这个时间默认是10秒。查看是否开启慢查询日志show variables like '%slow_query%';
查看慢查询设置的时长show variables like '%long_query_time%'
5.2 数据文件数据文件中主要包括了:db.opt文件、frm文件、MYD文件、MYI文件、ibd文件、ibdata文件、ibdata1文件、ib_logfile0和ib_logfile1文件等。db.opt文件主要记录当前数据库使用的字符集和检验规则等信息。frm文件存储数据表的结构信息,主要是数据表相关的元数据信息,包括数据表的表结构定义信息,每张表都会有一个frm文件。值得注意的是:MySQL8版本中的innodb存储引擎的表没有frm文件。(后面专门写一些MySQL8新特性的文章,从使用到底层原理与MySQL5到底有何不同)。MYD文件MyISAM存储引擎专用的文件格式,主要存放MyISAM存储引擎数据表中的数据,每张MyISAM存储引擎表对应一个.MYD文件。MYI文件MyISAM存储引擎专用的文件格式,主要存放与MyISAM存储引擎数据表相关的索引信息,每张MyISAM存储引擎表对应一个.MYI文件。ibd文件存放Innodb存储引擎的数据文件和索引文件,主要存放的是独享表空间的数据和索引,每张表对应一个.ibd文件。ibdata文件存放Innodb存储引擎的数据文件和索引文件,主要存放的是共享表空间的数据和索引,所有表共用一个(或者多个).ibdata文件,可以根据配置来指定共用的.ibdata文件个数。ibdata1文件MySQL的系统表空间数据文件,主要存储MySQL的数据表元数据、Undo日志等信息。ib_logfile0和ib_logfile1文件MySQL数据库中的Redo log文件,主要用于MySQL实现事务的持久性。如果在某个时间点MySQL发生了故障,此时如果有脏页没有写入到数据库的ibd文件中,在重启MySQL的时候,MySQL会根据Redo Log信息进行重做,将写入Redo Log并且尚未写入数据表的数据进行持久化操作。5.3 配置文件用于存在MySQL所有的配置信息,在Unix/Linux环境中是my,cnf文件,在Windows环境中是my.ini文件。5.4 pid文件pid文件是存放MySQL进程运行时的进程号的文件,主要存在于Unix/Linux环境中,具体的存储目录可以在my.cnf或者my.ini文件中进行配置。5.5 socket文件socket文件和pid文件一样,都是MySQL在Unix/Linux环境中运行才会有的文件。在Unix/Linux环境中,客户端可以直接通过socket来连接MySQL。
无事小神仙
什么是大事务?以及大事务产生的问题
一、定义运行时间比较长,操作的数据比较多的事务我们称之为大事务。例如,执行超过5s,10s,1min…二、大事务风险锁定太多的数据,造成大量的阻塞和锁超时,回滚所需要的时间比较长。执行时间长,容易造成主从延迟。undo log膨胀三、避免大事务避免一次处理太多大数据。移出不必要在事务中的select操作我这里按公司实际场景,规定了,每次操作/获取数据量应该少于5000条,结果集应该小于2M四、案例创建表建表sqlCREATE TABLE `apple_test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) NOT NULL DEFAULT '0' COMMENT 'a',
`b` int(11) NOT NULL DEFAULT '0' COMMENT 'b',
`updated_ts` timestamp(6) NOT NULL DEFAULT '0000-00-00 00:00:00.000000' ON UPDATE CURRENT_TIMESTAMP(6),
`created_ts` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入大量数据,模拟大事务首先插入一条记录insert into apple_test(`a`, `b`) values(1,1);
接着反复执行如下SQL,插入大量数据记录insert into apple_test(a, b) select a,b from apple_test;
在反复执行的过程中,我们会发现,执行耗时越来越长。这是因为每次插入的数据量越来越大。例如,当我们多次执行,数量达到8388608时,select count(*) from apple_test;
+----------+
| count(*) |
+----------+
| 8388608 |
+----------+
1 row in set (1.87 sec)
再次执行插入时,耗时会是几十秒,甚至几分钟:insert into apple_test(a, b) select a,b from test_test;
Query OK, 8388608 rows affected (1 min 20.68 sec)
Records: 8388608 Duplicates: 0 Warnings: 0
实际上,上面的插入语句,就是一个事务。执行过程耗时较长时,模拟的也就是大事务。查看大事务select a.trx_started,now(),(UNIX_TIMESTAMP(now()) - UNIX_TIMESTAMP(a.trx_started)) diff_sec,b.id,b.user,b.host,b.db,d.SQL_TEXT from information_schema.innodb_trx a inner join information_schema.PROCESSLIST b on a.TRX_MYSQL_THREAD_ID=b.id and b.command in('Query', 'Sleep', 'Connect') inner join performance_schema.threads c ON b.id = c.PROCESSLIST_ID inner join performance_schema.events_statements_current d ON d.THREAD_ID = c.THREAD_ID;
+---------------------+---------------------+----------+--------+-------------+------+------+----------+
| trx_started | now() | diff_sec | id | user | host | db | SQL_TEXT |
+---------------------+---------------------+----------+--------+-------------+------+------+----------+
| 2021-08-07 22:12:20 | 2021-08-07 22:13:26 | 66 | 808786 | system user | | NULL | BEGIN |
+---------------------+---------------------+----------+--------+-------------+------+------+----------+
1 row in set (0.01 sec)
五、大表带来的问题大表定义:单表超过千万行、文件超过10G大表对查询的影响: 慢查询、 区分度底、 大量磁盘IO、 建立索引需要很长的时间、 修改表结构需要长时间锁表、 影响正常的数据操作如何处理大表问题: 分库分表把一张表分成多个小表。难点:分表主键的选择、分表后夸分区数据的查询和统计。 大表的历史数据归档(前端增加历史查询)难点:时间点选择,如何进行归档操作六、在大促中数据库服务器数据库架构:主从复制、读写分离、集群等。TPS:每秒处理事务的速度(一个事务三个过程)。 用户请求服务器 服务器内部处理 服务器返回给用户。QPS:是一台指定服务器每秒能够相应的查询次数。并发量:同一时间处理的请求的数量。连接数:和服务器进行连接,但大部分处于sleep状态,只有少部分在运行。并发量大,连接数大说明cpu空闲少繁忙。磁盘IO读写过高会对服务器性能能造成影响。不要在主库上数据库备份(磁盘读压力增大)。
无事小神仙
Oracle查锁表(史上最全)
Oracle分两种锁,一种是DDL锁,一种是DML锁。一、Oracle DDL锁的解锁(dba_ddl_locks视图)1.1、查表的DDL锁的详情(kill session脚本、表名、执行锁表的SQL等)查DDL锁的数据字典,SQL如下:SELECT DISTINCT 'alter system kill session ''' || s.sid || ',' || s.serial# || ',@' ||
s.inst_id || ''' immediate;' AS kill_session_scripts
,s.sql_id
,a.sql_text
,s.sid
,s.serial#
FROM dba_ddl_locks l
,gv$session s
,gv$sqlarea a
WHERE 1 = 1
AND l.session_id = s.sid
AND s.sql_id = a.sql_id
AND lower(a.sql_text) NOT LIKE '%alter system kill session %'
-- AND l.owner IN ('TZQ','LOG')
;
查表的DDL锁的详情的查询结果如下图所示:.2、解锁表的DDL锁有两种方式可以解锁表的DDL锁一是:执行kill session脚本。二是:调用tzq_server_pkg包的kill_session存过执行杀会话kill session。1.2.1、解锁表的DDL锁 - 1、执行kill session脚本Ⅰ、打开命令窗口Ⅱ、执行上面生成好的kill session脚本alter system kill session '314,93,@1' immediate;1.2.2、解锁表的DDL锁 - 2、调用tzq_server_pkg包的kill_session存过执行杀会话kill session。tzq_server_pkg包的代码详见博客:Oracle解锁表、包、用户、杀会话、停job打开命令行窗口,执行下面命令:set serveroutput on
execute sys.tzq_server_pkg.kill_session(6335,15519);
二、Oracle DML锁的解锁(gv$locked_object视图)2.1、查表的DML锁的详情(kill session脚本、表名、执行锁表的SQL等)查DML锁的数据字典,SQL如下:SELECT DISTINCT 'alter system kill session ''' || s.sid || ',' || s.serial# || ',@' ||
s.inst_id || ''' immediate;' AS kill_session_scripts
,o.owner
,o.object_name
,s.sql_id
,a.sql_text
,s.sid
,s.serial#
FROM gv$locked_object l
,dba_objects o
,gv$session s
,gv$sqlarea a
WHERE l.object_id = o.object_id
AND l.session_id = s.sid
AND l.inst_id = s.inst_id
AND s.sql_id = a.sql_id
-- AND o.owner IN ('TZQ','LOG')
;
查表的DML锁的详情的查询结果如下图所示:2.2、解锁表的DML锁有两种方式可以解锁表的DML锁。一是:执行kill session脚本。二是:调用tzq_server_pkg包的kill_session存过执行杀会话kill session。2.2.1、解锁表的DML锁 - 1、执行kill session脚本Ⅰ、打开命令窗口Ⅱ、执行上面生成好的kill session脚本alter system kill session '314,93,@1' immediate;2.2.2、解锁表的DML锁 - 2、调用tzq_server_pkg包的kill_session存过执行杀会话kill session。打开命令行窗口,执行下面命令:set serveroutput on
execute sys.tzq_server_pkg.kill_session(6335,15519);三、附录3.1、根据sid查sql_text(gv$session、gv$sqlarea)SELECT s.sid
,s.serial#
,s.sql_id
,s.sql_hash_value
,s.username
,a.sql_text
FROM gv$session s
LEFT JOIN gv$sqlarea a
ON s.sql_id = a.sql_id
WHERE s.sql_id IS NOT NULL
AND a.sql_text NOT LIKE '%AND a.sql_text NOT LIKE %'
;
3.2、查锁表的详情(dba_locks视图)SELECT DISTINCT 'alter system kill session ''' || s.sid || ',' || s.serial# || ',@' ||
s.inst_id || ''' immediate;' AS kill_session_scripts
,l.session_id
,s.serial#
,l.lock_TYPE
,l.mode_held
,l.mode_requested
,CASE
WHEN o1.object_name IS NOT NULL
THEN o1.owner||'.'||o1.object_name
ELSE NULL
END AS id1_object_name
,CASE
WHEN o2.object_name IS NOT NULL
THEN o2.owner||'.'||o2.object_name
ELSE NULL
END AS id2_object_name
,l.last_convert
,l.blocking_others
,a.SQL_TEXT
FROM dba_locks l
LEFT JOIN dba_objects o1
ON l.lock_id1 = o1.OBJECT_ID
LEFT JOIN dba_objects o2
ON l.lock_id2 = o2.OBJECT_ID
LEFT JOIN gv$session s
ON l.session_id = s.SID
LEFT JOIN v$sqlarea a
ON s.sql_id = a.sql_id
WHERE 1=1
AND a.SQL_TEXT IS NOT NULL
AND (o1.owner IN ('TZQ','LOG') OR
o2.owner IN ('TZQ','LOG'))
;
3.3、Oracle查询锁定表的会话信息(gv$session、gv$process、gv$sqlarea)Oracle查询锁定表的会话信息,可以执行下面的SQL来进行查询:SELECT s.sid
,s.serial#
,p.spid
,s.username
,s.osuser
,s.program
,s.module
,s.action
,s.logon_time
,s.type
,a.sql_text
FROM gv$session s
,gv$process p
,gv$sqlarea a
WHERE s.paddr = p.addr
AND s.sql_id = a.sql_id
AND s.status = 'ACTIVE'
AND s.username IS NOT NULL
AND s.type != 'BACKGROUND'
AND a.sql_text NOT LIKE '%gv$sqlarea a%'
ORDER BY s.logon_time DESC;
3.4、gv$lock视图此查询将返回被锁定的表的会话ID、用户名、机器名、锁模式、锁定类型以及锁定对象的ID等信息。请注意,如果有多个锁定类型,则此查询可能会返回多行。SELECT s.sid
,s.serial#
,s.username
,s.osuser
,s.machine
,l.type
,l.block
,l.id1
,l.id2
,a.SQL_TEXT
,CASE
WHEN o1.object_name IS NOT NULL
THEN o1.owner||'.'||o1.object_name
ELSE NULL
END AS id1_object_name
,CASE
WHEN o2.object_name IS NOT NULL
THEN o2.owner||'.'||o2.object_name
ELSE NULL
END AS id2_object_name
FROM gv$session s
,gv$lock l
,gv$sqlarea a
,dba_objects o1
,dba_objects o2
WHERE s.sid = l.sid
AND s.sql_id = a.sql_id
AND l.id1 = o1.OBJECT_ID(+)
AND l.id2 = o2.OBJECT_ID(+)
AND a.SQL_TEXT NOT LIKE '%,gv$sqlarea a%'
;
查询结果如下图:
无事小神仙
MySQL 读写分离原理
读写分离概念基于主从复制的读写分离,是我们在单机环境下,数据库的性能到瓶颈了,可以通过读写分离,提高后台服务性能。存储这一块的增删改查的并发的处理能力,主库专门负责相对少的写操作,从库专门负责相对多的读操作,主库的数据更改通过主从复制同步到从库读写分离就是在主服务器上修改,数据会同步到从服务器,从服务器只能提供读取数据,不能写入,实现备份的同时也实现了数据库性能的优化,以及提升了服务器安全MySQL client通过mysql 提供的API,用mysql自定义的基于TCP的数据协议(简称mysql协议)与MySQL Server通信,访问MySQL Server数据库最初,我们只有一台MySQL服务器,所有数据的增删改查都是在一台机器上进行,随着服务越来越多的人使用,流量越来越大,需要并发能力的不断提升,如果数据库的性能到瓶颈了,我们就要进行读写分离的操作图中的MySQL主服务器专门做写操作,下面连着2个MySQL从服务器专门做读操作,读请求转发到B、C,写请求转发到A如果我们在客户端上直接用代码写死,insert、update等写操作,在A上做,show、select等读操作在B、C上做,相当于代码和主从环境就是强绑定的。这就导致代码的稳定性不太好,因为和环境强相关了,我们写代码得时候必须得知道哪个机器是负责写操作的主库,哪个机器是负责读操作的从库。而这时如果有某个机器挂掉了,代码也不会知道,还是按照原来的方式转发请求,通信就会出现问题,所以把读写分离用代码实现肯定不合适引入中间件MyCat这时候就需要引入数据库中间件了,实际上,读写分离,分库分表都是需要依赖数据库中间件(mycat),mycat就是反向代理服务器客户端实际上区分不出来连的是MyCat还是MySQL,因为通信都是遵守的是MySQL通信协议,之前怎么和MySQL通信,现在就怎么和MyCat通信,所以不用进行区分在MyCat上配置读写分离,我们在客户端上的代码不用做任何变更,代码上不需要区分哪个请求是读,哪个请求是写,代码直接访问的是MyCat,由MyCat解析请求,根据SQL的读写性质转发到负责相应操作的服务器,实现读写分离在MyCat上就是要配置主服务器和从服务器的信息,有3种情况:一主一从、一主多从、多主多从一主多从场景:当写库(master)挂了,MyCat还可以马上把一个从库(slave)直接变成一个写库(master),然后变成写库的从库还要和其他从库之间配置一下主从复制多主多从场景:可以看到图中,MyCat服务器挂了两套环境,如果其中1套的主库宕机了(它对应的从库也就不能用了),此时MyCat会自动切到另一套环境,因为M1和M2之间也是配置成互为主从的,所以M2可以同步M1的数据,提供与M1环境完全相同的服务MyCat服务端口和管理端口MySQL的服务端口是3306,MyCat服务端口是8066(这个端口也是可以改的),也就是MySQL Client连接的是8066端口,登录8066端口看到的界面就和登录MySQL Server的3306端口一样MyCat还有一个管理端口9066,登录这个管理端口可以查看MyCat正在工作的所有状态以及和后端服务器的连接,以及连接数据源的状态等。
无事小神仙
MySQL事务的隔离级别
一、隔离级别概念事务的隔离级别就是对事务并发的控制MySQL支持的四种隔离级别是:TRANSACTION_READ_UNCOMMITTED:未提交读。说明在提交前事务A可以看到事务B的变化。这样脏读,不可重复读和幻读都是可能发生的。TRANSACTION_READ_COMMITTED:已提交读(oracle默认),说明读取未提交的数据是不允许的(防止脏读)。在这个级别不可重复读和幻读可能发生的。TRANSACTION_REPEATABLE_READ:可重复读(MySQL默认),说明事务保证能够再次读取相同的数据而不会失败,即使其他的事务把这个数据改了,你也不会看到前后两次查询的数据的不同,完全可以保证两次查询的结果是一样的,但是幻读仍然会出现。TRANSACTION_SERIALIZABLE:串行化,是最高的事务隔离级别,它防止脏读,不可重复读和幻读。串行执行,相当于是单线程操作,并发能力最低事务隔离级别越高,为避免冲突所花费的性能也就越多在可重复读级别,实际上可以解决部分的幻读问题,但是不能防止update更新产生的幻读问题,要禁止幻读产生,还是需要设置串行化隔离级别两个MySQL客户端默认工作在可重复读级别二、测试READ-UNCOMMITTED隔离级别先设置为最低的隔离级别:未提交读若此时A客户端rollback了,数据库中zhangsan的年龄恢复成了20,那这时候已经来不及了,B客户端拿着年龄21去做业务了两个客户端都rollback,放弃当前事务对数据做出的改变,zhangsan的年龄恢复为20三、测试TRANSACTION_READ_COMMITTED隔离级别由于设置了已提交读隔离级别,事务B并没有发生脏读,这是由各种锁机制以及事务并发的MVCC版本控制实现的查询到了已经commit的数据,发生了不可重复读,这在已提交读隔离级别是允许发生的既然发生了不可重复读,幻读就肯定可以发生了四、测试TRANSACTION_REPEATABLE_READ隔离级别提交刚才事务,设置可重复读隔离级别可重复读隔离级别:对于一个事务来说,可以放心读数据,就算有其他事务修改了数据并且已经提交了,也不会在当前事务表现出来。只要自己没改,数据都是不会变的在可重复读隔离级别,测试幻读(在一定程度上防止了幻读,但没有完全防止)可以看到,在当前的可重复读隔离级别,右侧事务无法查询到左侧事务insert的数据,虽然看不到,但由于左侧事务已经提交,数据库表中是存在name为aaa的数据的,由于MVCC控制,右侧事务无法看见。但可以直接操作这条看不见的数据,操作以后,数据可以出现右边的客户端update左侧客户端insert的数据:实际上,事务A已经插入并且提交了,aaa已经存在,因为事务B update aaa的年龄成功了前后两次同样的查询,后一次查询与前一次查询的数据量不同,就发生了幻读。也就是可重复读隔离级别下,并没有解决幻读的问题,要彻底解决幻读,就需要设置串行化隔离级别五、测试TRANSACTION_SERIALIZABLE隔离级别由于事务B正在读数据,此时事务A再写数据就被阻塞了(用读写锁实现,允许读读,不允许读写或者写写)MySQL server不会让自己执行事务的线程永远阻塞,导致当前线程占用的锁无法释放,而使得其他执行事务的线程也无法获得锁而永远阻塞。所以当线程等待时间过长时,会让超时线程释放锁,并会返回一个错误:
无事小神仙
Oracle数据库修改进程连接数
一、查询Oracle数据库进程连接数首选用Oracle工具PL/SQL登录到Oracle数据库,执行下面的命令,查看Oracle当前的进程连接最大数量:select value,t.* from v$parameter t where name = 'processes';
我们可以看到,Oracle数据库默认只支持最大150个客户端连接。如果是生产环境,这些数量肯定是不够的。下面我们来修改这个参数,把它改到500。二、修改Oracle数据库进程连接数为500继续在PL/SQL工具的SQL窗口中执行下面命令,修改Oracle的连接数为500:alter system set processes=500 scope=spfile;
执行成功后,直接查第一步的语句发现还是150。我们需要重启Oracle数据库使这个配置生效。。三、Windows下重启Oracle数据库,使配置生效3.1、关闭Oracle数据库监听服务以管理员权限打开命令窗口,使用lsnrctl命令来关闭监听服务,输入:lsnrctl stop关闭Oracle的监听器服务后,我们下一步需要登录Oracle数据库,用命令停止Oracle实例。3.2、用sqlplus以sysdba身份登录到Oracle数据库以管理员权限打开命令窗口,输入:sqlplus / as sysdba以sysdba身份登录到数据库。3.3、立即关闭Oracle数据库登录到数据库后,我们来把Oracle数据库关闭掉,输入命令:shutdown immediate我们可以看到,数据库已关闭。已经卸载数据库。ORACLE例程已经关闭。这就代表Oracle数据库已经被我们关闭掉了。现在再去用PL/SQL登录Oracle数据库发现已经登录不上了。3.4、退出sqlplus,回到命令行现在我们来退回到命令行,继续输入:exit退出了sqlplus。3.5、启动Oracle数据库监听服务我们在命令行,重新把Oracle监听服务开起来。输入命令:lsnrctl start查看监听发现没有运行任何实例。是的,上面我们已经把数据库关闭了,所以没有实例是正常的。3.6、用sqlplus以sysdba身份登录到Oracle数据库然后,我们需要重新登录到Oracle数据库中,把Oracle‘数据库实例启动起来。执行以下命令:sqlplus / as sysdba我们用sqlplus命令登录到了数据库。3.7、启动Oracle数据库我们继续把Oracle数据库实例启动起来,继续输入命令:startup我们能现在可以看到,Oracle数据库已经装载完毕、数据库已经打开。3.8、退出sqlplus,回到命令行我们再退回到命令行。3.9、查看Oracle数据库监听服务状态我们再把Oracle数据库监听服务的状态查看一下,输入命令:lsnrctl status我们可以看到,现在Oracle监听服务已经能够发现“tzq”这个实例了。说明数据库运行正常。四、重新查询Oracle数据库进程连接数我们重新用PL/SQL登录到Oracle数据库,再次查看Oracle进程最大连接数,执行以下查询SQL:select value,t.* from v$parameter t where name = 'processes';发现Oracle进程最大连接数已经被修改成了500。至此,Oracle修改进程连接数教程也演示完毕!
无事小神仙
MySQL SQL和索引优化总结
首先我们需要知道MySQL主要是从以下3个方面进行优化:SQL语句和索引应用优化(引入缓存、连接池)配置参数优化一、SQL语句和索引的优化当数据量比较大,若SQL语句写的不合适,会导致SQL的执行效率低,我们需要等待很长时间才能拿到结果针对性优化的时候,若数据量太大,可通过limit分页explain分析的时候可能出现以下问题:没有用索引用多列索引没有用到第一列,导致没用到索引联合查询的大小表设置不合理,导致索引没用上(小表是整表查询,大表才用索引)多表查询不用in(产生中间表),用外连接替代带in子查询的过程,合理使用索引二、应用优化除了优化SQL和索引,在实际生产环境中,由于数据库服务器本身的性能局限,就必须要对上层的应用来进行一些优化,使得上层应用访问数据库的压力能够减到最小引入数据库连接池;防止客户端不断三次握手建立连接,四次挥手关闭连接,耗费网络以及服务器资源,我们可以引入数据库连接池,这是高并发场景下常用的一种优化手段(需要设置初始连接量,最大连接量以及最大空闲时间等参数)引入缓存;用于存储热点数据,如果客户端的请求来了,先在redis上查一下(redis是基于内存的数据库),如果redis上直接查到就不经过MySQL数据库,如果没有查到就去访问MySQL数据库,访问MySQL完成后,先把当前访问的数据往redis上缓存一下,再把结果返回给用户引入redis缓存的话,也会有一些附带的问题:缓存数据一致性问题,缓存穿透和缓存雪崩等等三、MySQL Server优化对于MySQL Server端的优化,主要指的是MySQL Server启动时加载的配置文件(my.ini或my.cnf)中配置项的优化1. 自适应哈希索引由于hash索引的生成和维护也是耗费性能的,通过以下命令查看自适应哈希索引搜索的频率低于使用二级索引树搜索的频率:show engine innodb status\G
如果使用自适应哈希索引搜索的频率较低,可以通过变量innodb_adaptive_hash_index关闭自适应哈希索引2. redo log可以根据物理机的条件,合理设置InnoDB log buffer大小(redo log缓存的大小),Innodb_buffer_pool_size(缓存的大小),来减少磁盘I/O次数,因为缓存区大了,在缓冲区工作的时间就长了,redo log的效率就高了my.cnf配置参数如下:3. MySQL查询缓存MySQL的查询缓存是把上一次select的查询结果记录下来放在缓存当中,下一次再查询相同内容的时候,直接从缓存中取出来就可以了,不用再进行一遍真正的SQL查询(在内存中划分一块空间用做缓存的地方)查询缓存适用于更新不频繁的表,查询频繁的表,因为当两个select查询中间出现insert,update,delete语句的时候,查询缓存就会被清空,过多的查询缓存的数据添加和删除,就会影响MySQL的执行效率,可能还不如每次都从磁盘上查询可以通过以下命令,来查看查询缓存的设置:如果某个表的查询多而更改少,可以考虑开启查询缓存通过show status命令,可以查看MySQL查询缓存的使用状况,如下:可以通过set命令设置上面的缓存参数开启MySQL查询缓存功能,也可以找到MySQL的配置文件(windows是my.ini,linux是my.cnf),修改query_cache_type参数为1就可以了,然后重启MySQL Server就可以使用了,如下:MySQL查询缓存还是不如使用redis,这是MySQL的查询缓存,我们作为MySQL服务的使用者来说,控制不了MySQL的缓存方式,如果我们引入redis的话,可以通过redis提供的API精确控制要缓存的数据,以及不缓存的数据4. 索引和数据缓存主要指的就是innodb_buffer_pool_size配置项,从名字上就能看到,该配置项是针对InnoDB存储引擎起作用的,这个参数定义了InnoDB存储引擎的表数据和索引数据的最大内存缓冲区大小。innodb_buffer_pool_size是同时为数据块和索引块做缓存,这个值设的越高,访问表中数据需要的磁盘I/O就越少innodb_buffer_pool_size = 402653184 -- 384M
5. MySQL线程缓存主要指配置文件中thread_cache_size配置项MySQL Server网络模块采用经典的select I/O复用+线程池模型,之所以引入线程池,主要就是为了在业务执行的过程中,不会因为临时创建和销毁线程,造成系统性能降低,因为线程的创建和销毁是很耗费性能的线程池就是在业务使用之前,先创建一组固定数量的线程,等待事件发生,当有SQL请求到达MySQL Server的时候,在线程池中取一个线程来执行该SQL请求就可以了,执行完成后,不销毁线程,而是把线程再归还到线程池中,等待下一次任务的处理(线程池的线程数量随着请求越来越多,是可以动态增加的)配置完thread_cache_size,重启MySQL Server服务后即可生效6. 并发连接数量和超时时间MySQL Server作为一个服务器,可以设置客户端的最大连接量和连接超时时间,如果数据库连接统计数量比较大,这两个参数的值需要设置大一些在配置文件(my.cnf或my.ini)最下面,添加配置:max_connections=1000,然后重启MySQLServer,配置生效MySQL Server对于长时间未通信的连接,会主动关闭连接。设置超时时间,超过设置时间没有请求就主动断开,单位是秒,在配置文件中添加配置:wait_timeout = 600
无事小神仙
DDL、DML和DCL的区别与理解
DML、DDL、DCL区别 .总体解释:DML(data manipulation language):它们是SELECT、UPDATE、INSERT、DELETE,就象它的名字一样,这4条命令是用来对数据库里的数据进行操作的语言DDL(data definition language):DDL比DML要多,主要的命令有CREATE、ALTER、DROP等,DDL主要是用在定义或改变表 (TABLE)的结构,数据类型,表之间的链接和约束等初始化工作上,他们大多在建立表时使用DCL(Data Control Language):是数据库控制功能。是用来设置或更改数据库用户或角色权限的语句,包括 (grant,deny,revoke等)语句。在默认状态下,只有sysadmin,dbcreator,db_owner或db_securityadmin等人员才有权力执行DCL详细解释:一、DDL is Data Definition Language statements. Some examples:数据定义语言,用于定义和管理 SQL 数据库中的所有对象的语言CREATE - to create objects in the database 创建ALTER - alters the structure of the database 修改DROP - delete objects from the database 删除TRUNCATE - remove all records from a table, including all spaces allocated for the records are removedTRUNCATE TABLE [Table Name]。 下面是对Truncate语句在MSSQLServer2000中用法和原理的说明: Truncate table 表名 速度快,而且效率高,因为: TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同:二者均删除表中的全部行。但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少。 DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一项。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。 TRUNCATE TABLE 删除表中的所有行,但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值,请改用 DELETE。如果要删除表定义及其数据,请使用 DROP TABLE 语句。 对于由 FOREIGN KEY 约束引用的表,不能使用 TRUNCATE TABLE,而应使用不带 WHERE 子句的 DELETE 语句。由于 TRUNCATE TABLE 不记录在日志中,所以它不能激活触发器。 TRUNCATE TABLE 不能用于参与了索引视图的表。COMMENT - add comments to the data dictionary 注释GRANT - gives user’s access privileges to database 授权REVOKE - withdraw access privileges given with the GRANT command 收回已经授予的权限二、DML is Data Manipulation Language statements. Some examples:数据操作语言,SQL中处理数据等操作统称为数据操纵语言SELECT - retrieve data from the a database 查询INSERT - insert data into a table 添加UPDATE - updates existing data within a table 更新DELETE - deletes all records from a table, the space for the records remain 删除CALL - call a PL/SQL or Java subprogramEXPLAIN PLAN - explain access path to dataOracle RDBMS执行每一条SQL语句,都必须经过Oracle优化器的评估。所以,了解优化器是如何选择(搜索)路径以及索引是如何被使用的,对优化SQL语句有很大的帮助。Explain可以用来迅速方便地查出对于给定SQL语句中的查询数据是如何得到的即搜索路径(我们通常称为Access Path)。从而使我们选择最优的查询方式达到最大的优化效果。LOCK TABLE - control concurrency 锁,用于控制并发三、DCL is Data Control Language statements. Some examples:数据控制语言,用来授予或回收访问数据库的某种特权,并控制数据库操纵事务发生的时间及效果,对数据库实行监视等COMMIT - save work done 提交SAVEPOINT - identify a point in a transaction to which you can later roll back 保存点ROLLBACK - restore database to original since the last COMMIT 回滚SET TRANSACTION - Change transaction options like what rollback segment to use 设置当前事务的特性,它对后面的事务没有影响.DDL、DML和DCL的理解 (张青山)先给出一个图:1、DDL 1-1、DDL的概述
DDL(Data Definition Language 数据定义语言)用于操作对象和对象的属性,这种对象包括数据库本身,以及数据库对象,像:表、视图等等,DDL对这些对象和属性的管理和定义具体表现在Create、Drop和Alter上。特别注意:DDL操作的“对象”的概念,”对象“包括对象及对象的属性,而且对象最小也比记录大个层次。以表举例:Create创建数据表,Alter可以更改该表的字段,Drop可以删除这个表,从这里我们可以看到,DDL所站的高度,他不会对具体的数据进行操作。
1-2、DDL的主要语句(操作)
Create语句:可以创建数据库和数据库的一些对象。
Drop语句:可以删除数据表、索引、触发程序、条件约束以及数据表的权限等。
Alter语句:修改数据表定义及属性。
1-3、DDL的操作对象(表)
1-3-1、表的概念
表的创建就是用来存放数据用的,由于我们存放的数据的不通,所以我们需要定义些数据类型,以方便管理。
1-3-2、表的属性
主键属性:主键就是主键约束,只不过起的名字不同了,主键的起名偏向于虚的(就是描述描述这件事),主键约束起名偏向于实得(就是描述操作的实施),描述的都是同一件事,主键约束就是表中的一个属性;在一个表中最多可以有一个主键;一个主键可以定义在一个或多个字段;主键使一个或多个字段的值必须唯一且不为空,这样做可以通过该字段或该组字段中的值唯一的代表一条记录。
唯一属性:一个表中只能有一个主键属性,为了方表用户,提出唯一约束;唯一约束可以定义在一个或多个字段上;唯一约束使该字段或该组字段中的值唯一,可以为空,但是,不能重复。
外键属性:又叫外键,又叫外键约束,跟主键和主键约束的关系是一样的;外键约束针对的两个表,如果表A的主关键字是表B中的字段,则该字段称为表B的外键,表A称为主表,表B称为从表,但要注意,必须要计算机要知道你是这种关系。
核查、Null和缺省属性:核查属性又叫核查约束,Null属性又叫Null约束,缺省属性又叫缺省约束;这些名称是描述一件事,描述一种情况,这件事或这张情况我们当然可以人为的那样特意做(输入数据是注意就行),但是,他们的本意是实现自动化,也就是让计算机做这件事。
(你知道为什么建立主键和唯一约束的时候,会自动的创建索引吗?而且是唯一索引,想一想索引大多在那些字段上用,以及索引的作用就会知道了。像主键约束、唯一约束、非空约束、外键约束、核查约束和缺省约束这些操作都是使表具有某些特性,所以在这里我认为他们都是表的属性。)
2、DML 2-1、DML的概述
DML(Data Manipulation Language 数据操控语言)用于操作数据库对象中包含的数据,也就是说操作的单位是记录。
2-2、DML的主要语句(操作)
Insert语句:向数据表张插入一条记录。
Delete语句:删除数据表中的一条或多条记录,也可以删除数据表中的所有记录,但是,它的操作对象仍是记录。
Update语句:用于修改已存在表中的记录的内容。
2-3、DML的操作对象——记录
2-3-1、注意
当我们对记录进行Insert、Delete和Update操作的时候,一定要注意,一定要清楚DDL对其的一些操作。
3、DCL 3-1、DCL的概述
DCL(Data Control Language 数据控制语句)的操作是数据库对象的权限,这些操作的确定使数据更加的安全。
3-2、DCL的主要语句(操作)
Grant语句:允许对象的创建者给某用户或某组或所有用户(PUBLIC)某些特定的权限。
Revoke语句:可以废除某用户或某组或所有用户访问权限
3-3、DCL的操作对象(用户)
此时的用户指的是数据库用户
无事小神仙
分库分表常见问题和解决方案
前言MySQL出现的性能问题表数据量过大sql查询太复杂sql查询没走索引数据库服务器的性能过低等Mysql常见的优化手段增加索引,索引是直观也是最快速优化检索效率的方式。基于Sql语句的优化,比如最左匹配原则,用索引字段查询、降低sql语句的复杂度等。表的合理设计,比如符合三范式、或者为了一定的效率破坏三范式设计等。数据库参数优化,比如并发连接数、数据刷盘策略、调整缓存大小。数据库服务器硬件升级。mysql搭建主从复制方案,实现读写分离。大数据表优化方案对于大数据表的优化最直观的方式就是减少单表数据量,所以常见的解决方案是:分库分表,大表拆小表。冷热数据分离,所谓的冷热数据,其实就是根据访问频次来划分的,访问频次较多的数据是热数据,访问频次少的数据是冷数据。冷热数据分离就是把这两类数据分离到不同的表中,从而减少热数据表的大小。历史数据归档,简单来说就是把时间比较久远的数据分离出来存档,保证实时库的数据的有效生命周期。(磁带等,低价的存储介质)详解分库分表分库分表是非常常见针对单个数据表数据量过大的优化方式,它的核心思想是把一个大的数据表拆分成多个小的数据表,这个过程也叫(数据分片),它的本质其实有点类似于传统数据库中的分区表,比如mysql和oracle都支持分区表机制。分库分表是一种水平扩展手段,每个分片上包含原来总的数据集的一个子集。这种分而治之的思想在技术中很常见,比如多CPU、分布式架构、分布式缓存等等,像前面我们讲redis cluster集群时,slot槽的分配就是一种数据分片的思想。如图6-1所示,数据库分库分表一般有两种实现方式:垂直拆分垂直拆分有两种,一种是单库的垂直拆分,另一种是多个数据库的垂直拆分。单库垂直分表单个表的字段数量建议控制在20~50个之间,之所以建议做这个限制,是因为如果字段加上数据累计的长度超过一个阈值后,数据就不是存储在一个页上,就会产生分页的问题,而这个问题会导致查询性能下降。所以如果当某些业务表的字段过多时,我们一般会拆去垂直拆分的方式,把一个表的字段拆分成多个表,如图6-2所示,把一个订单表垂直拆分成一个订单主表和一个订单明细表。在Innodb引擎中,单表字段最大限制为1017,参考mysql官网。多库垂直分表多库垂直拆分实际上就是把存在于一个库中的多个表,按照一定的纬度拆分到多个库中,如图6-3所示。这种拆分方式在微服务架构中也是很常见,基本上会按照业务纬度拆分数据库,同样该纬度也会影响到微服务的拆分,基本上服务和数据库是独立的。多库垂直拆分最大的好处就是实现了业务数据的隔离。其次就是缓解了请求的压力,原本所有的表在一个库的时候,所有请求都会打到一个数据库服务器上,通过数据库的拆分,可以分摊掉请求,在这个层面上提升了数据库的吞吐能力。水平拆分垂直拆分的方式并没有解决单表数据量过大的问题,所以我们还需要通过水平拆分的方式把大表数据做数据分片。水平切分也可以分成两种,一种是单库的,一种是多库的。单库水平分表如图所示,表示把一张有10000条数据的用户表,按照某种规则拆分成了4张表,每张表的数据量是2500条。两个案例:银行的交易流水表,所有进出的交易都需要登记这张表,因为绝大部分时候客户都是查询当天的交易和一个月以内的交易数据,所以我们根据使用频率把这张表拆分成三张表:当天表:只存储当天的数据。当月表:我们在夜间运行一个定时任务,前一天的数据,全部迁移到当月表。用的是insert intoselect,然后delete。历史表:同样是通过定时任务,把登记时间超过30天的数据,迁移到history历史表(历史表的数据非常大,我们按照月度,每个月建立分区)。费用表: 消费金融公司跟线下商户合作,给客户办理了贷款以后,消费金融公司要给商户返费用,或者叫提成,每天都会产生很多的费用的数据。为了方便管理,我们每个月建立一张费用表,例如fee_detail_201901…fee_detail_201912。但是注意,跟分区一样,这种方式虽然可以一定程度解决单表查询性能的问题,但是并不能解决单机存储瓶颈的问题。多库水平分表多库水平分表,其实有点类似于分库分表的综合实现方案,从分表来说是减少了单表的数据量,从分库层面来说,降低了单个数据库访问的性能瓶颈,如图所示。常见的水平分表策略哈希取模分片哈希分片,其实就是通过表中的某一个字段进行hash算法得到一个哈希值,然后通过取模运算确定数据应该放在哪个分片中,如图所示。这种方式非常适合随机读写的场景中,它能够很好的将一个大表的数据随机分散到多个小表。hash取模的问题hash取模运算有个比较严重的问题,假设根据当前数据表的量以及增长情况,我们把一个大表拆分成了4个小表,看起来满足目前的需求,但是经过一段时间的运行后,发现四个表不够,需要再增加4个表来存储,这种情况下,就需要对原来的数据进行整体迁移,这个过程非常麻烦。一般为了减少这种方式带来的数据迁移的影响,我们会采用一致性hash算法。一致性hash算法按照范围分片按范围分片,其实就是基于数据表的业务特性,按照某种范围拆分,这个范围的有很多含义,比如:时间范围,比如我们按照数据创建时间,按照每一个月保存一个表。基于时间划分还可以用来做冷热数据分离,越早的数据访问频次越少。区域范围,区域一般指的是地理位置,比如一个表里面存储了来自全国各地的数据,如果数据量较大的情况下,可以按照地域来划分多个表。数据范围,比如根据某个字段的数据区间来进行划分。如图所示,表示按照数据范围进行拆分。分库分表实战假设存在一个用户表,用户表的字段如下。该表主要提供注册、登录、查询、修改等功能。该表的具体的业务情况如下(需要注意,在进行分表之前,需要了解业务层面对这个表的使用情况,然后再决定使用什么样的方案,否则脱离业务去设计技术方案是耍流氓)。用户端: 前台访问量较大,主要涉及两类请求:用户登录,面向C端,对可用性和一致性要求较高,主要通过login_name、email、phone来查询用户信息,1%的请求属于这种类型。用户信息查询,登录成功后,通过uid来查询用户信息,99%属于这种类型。运营端: 主要是运营后台的信息访问,需要支持根据性别、手机号、注册时间、用户昵称等进行分页查询,由于是内部系统,访问量较低,对可用性一致性要求不高。根据uid进行水平分表由于99%的请求是基于uid进行用户信息查询,所以毫无疑问我们选择使用uid进行水平分表。那么这里我们采用uid的hash取模方法来进行分表,具体的实施如图6-9所示,根据uid进行一致性hash取模运算得到目标表进行存储。按照上图的结构,分别复制user_info表,重新命名为01~04,如图所示。如何实现全局唯一ID数据库自增ID(定义全局表)UUIDRedis的原子递增Twitter-Snowflake算法美团的leafMongoDB的ObjectId百度的UidGenerator分布式ID的特性唯一性:确保生成的ID是全局唯一的。有序递增性:确保生成的ID是对于某个用户或者业务是按一定的数字有序递增的。高可用性:确保任何时候都能正确的生成ID。带时间:ID里面包含时间,一眼扫过去就知道哪天的数据。数据库自增方案在数据库中专门创建一张序列表,利用数据库表中的自增ID来为其他业务的数据生成一个全局ID,那么每次要用ID的时候,直接从这个表中获取即可。CREATE TABLE `uid_table` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`business_id` int(11) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE (business_type)
)
在应用程序中,每次调用下面这段代码,就可以持续获得一个递增的ID。begin;
REPLACE INTO uid_table (business_id) VALUES (2);
SELECT LAST_INSERT_ID();
commit;
其中,replace into是每次删除原来相同的数据,同时加1条,就能保证我们每次得到的就是一个自增的ID。这个方案的优点是非常简单,它也有缺点,就是对于数据库的压力比较大,而且最好是独立部署一个DB,而独立部署又会增加整体的成本。优点:非常简单,利用现有数据库系统的功能实现,成本小,有DBA专业维护。ID号单调自增,可以实现一些对ID有特殊要求的业务。缺点:强依赖DB,当DB异常时整个系统不可用,属于致命问题。配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号。ID发号性能瓶颈限制在单台MySQL的读写性能。UUIDUUID的格式是: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 8-4-4-4-12共36个字符,它是一个128bit的二进制转化为16进制的32个字符,然后用4个 - 连接起来的字符串。UUID的五种生成方式:基于时间的UUID(date-time & MAC address): 主要依赖当前的时间戳及机器mac地址,因此可以保证全球唯一性。(使用了Mac地址,因此会暴露Mac地址和生成时间。)分布式安全的UUID(date-time & group/user id)将版本1的时间戳前四位换为POSIX的UID或GID。基于名字空间的UUID-MD5版(MD5 hash & namespace),基于指定的名字空间/名字生成MD5散列值得到,标准不推荐。基于随机数的UUID(pseudo-random number):基于随机数或伪随机数生成。基于名字空间的UUID-SHA1版(SHA-1 hash & namespace):将版本3的散列算法改为SHA1。在Java中,提供了基于MD5算法的UUID、以及基于随机数的UUID。优点:本地生成,没有网络消耗,生成简单,没有高可用风险。缺点:不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。无序查询效率低:由于生成的UUID是无序不可读的字符串,所以其查询效率低。UUID不适合用来做数据库的唯一ID,如果用UUID做主键,无序的不递增,大家都知道,主键是有索引的,然后mysql的索引是通过b+树来实现的,每一次新的UUID数据的插入,为了查询的优化,都会对索引底层的b+树进行修改,因为UUID数据是无序的,所以每一次UUID数据的插入都会对主键的b+树进行很大的修改,严重影响性能。雪花算法SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id。雪花算法比较常见,在百度的UidGenerator、美团的Leaf中,都有用到雪花算法的实现。如图6-11所示,表示雪花算法的组成,一共64bit,这64个bit位由四个部分组成。第一部分, 1bit位,用来表示符号位,而ID一般是正数,所以这个符号位一般情况下是0。第二部分, 占41 个 bit:表示的是时间戳,是系统时间的毫秒数,但是这个时间戳不是当前系统的时间,而是当前 系统时间-开始时间 ,更大的保证这个ID生成方案的使用的时间!那么我们为什么需要这个时间戳,目的是为了保证有序性,可读性,我一看我就能猜到ID是什么时候生成的。41位可以2 41 - 1表示个数字,如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 2 41 -1,减1是因为可表示的数值范围是从0开始算的,而不是1。也就是说41位可以表示2 41 -1个毫秒的值,转化成单位年则是(2 41 -1)/1000 * 60 * 60 * 24*365=69年,也就是能容纳69年的时间第三部分, 用来记录工作机器id,id包含10bit,意味着这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器。其中这10bit又可以分成2个5bit,前5bit表示机房id、5bit表示机器id,意味着最多支持2^5个机房(32),每个机房可以支持32台机器。第四部分, 第四部分由12bit组成,它表示一个递增序列,用来记录同毫秒内产生的不同id。那么我们为什么需要这个序列号,设想下,如果是同一毫秒同一台机器来请求,那么我们怎么保证他的唯一性,这个时候,我们就能用到我们的序列号,目的是为了保证同一毫秒内同一机器生成的ID是唯一的,这个其实就是为了满足我们ID的这个高并发,就是保证我同一毫秒进来的并发场景的唯一性。12位(bit)可以表示的最大正整数是2^12-1=4095,即可以用0、1、2、3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。12位2进制,如果全部都是1的情况下,那么最终的值就是4095,也就是12bit能够存储的最大的数字是4095.优点:毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。作为DB表的主键,索引效率高。不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。高性能高可用:生成时不依赖于数据库,完全在内存中生成。容量大,每秒中能生成数百万的自增ID。可以根据自身业务特性分配bit位,非常灵活。缺点:强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。不是严格全局递增的。原标准实现代码中是直接抛异常,短暂停止对外服务,这样在实际生产中是无法忍受的。时钟回拨问题由于雪花算法是依赖于服务器的时间的,所以如果机器发生了故障或者别的情况,对服务器的时间进行了回拨,那么会导致生成的ID可能发生重复。解决方案保存过去一段时间内每一台机器在当前这一毫秒产生的ID的最大值,比如使用Map形式,就是<machine_id,max_id>,这样如果某台机器发生了时钟回拨,直接在这台机器对应的max_id的基础上继续自增生成ID即可。(自己存一下使用过的时间戳,每次新建ID的时候用新时间戳和使用过的最大时间戳对比)不依赖机器时钟驱动,就没时钟回拨的事儿了。即定义一个初始时间戳,在初始时间戳上自增,不跟随机器时钟增加。时间戳何时自增?当序列号增加到最大时,此时时间戳+1,这样完全不会浪费序列号,适合流量较大的场景,如果流量较小,可能出现时间断层滞后。依然依赖机器时钟,如果时钟回拨范围较小,如几十毫秒,可以等到时间回到正常;如果流量不大,前几百毫秒或者几秒的序列号肯定有剩余,可以将前几百毫秒或者几秒的序列号缓存起来,如果发生时钟回拨,就从缓存中获取序列号自增。非分片键查询我们对user_info表的分片,是基于biz_id来实现的,也就是意味着如果我们想查询某张表的数据,必须先要使用biz_id路由找到对应的表才能查询到。那么问题来了,如果查询的字段不是分片键(也就是不是biz_id),比如本次分库分表实战案例中,运营端查询就有根据名字、手机号、性别等字段来查,这时候我们并不知道去哪张表查询这些信息。非分片键和分片键建立映射关系第一种解决办法就是,把非分片键和分片键建立映射关系,比如login_name -> biz_id 建立映射,相当于建立一个简单的索引,当基于login_name查询数据时,先通过映射表查询出login_name对应的biz_id,再通过biz_id定位到目标表。映射表的只有两列,可以承载很多的数据,当数据量过大时,也可以对映射表做水平拆分。 同时这种映射关系其实就是k-v键值对的关系,所以我们可以使用k-v缓存来存储提升性能。同时因为这种映射关系的变更频率很低,所以缓存命中率很高,性能也很好。用户端数据库和运营端数据库进行分离运营端的查询可能不止于单个字段的映射来查询,可能更多的会涉及到一些复杂查询,以及分页查询等,这种查询本身对数据库性能影响较大,很可能影响到用户端对于用户表的操作,所以一般主流的解决方案就是把两个库进行分离。由于运营端对于数据的一致性和可用性要求不是很高,也不需要实时访问数据库,所以我们可以把C端用户表的数据同步到运营端的用户表,而且用户表可以不需要做分表操作,直接全量查表即可。当然,如果运营端的操作性能实在是太慢了,我们还可以采用ElasticSearch搜索引擎来满足后台复杂查询的需求。实际应用中会遇到的问题数据迁移解决方案
无事小神仙
数据库设计的基本原则和主要步骤以及应注意什么?
前言在数据库设计的时候一般要遵循一些规范,也是工作中总结出来的经验,拿来和大家分享一、数据库设计的基本原则把具有同一个主题的数据存储在一个数据表中,“一表一用”。尽量消除冗余,提高访问数据库的速度。一般要求数据库设计达到第三范式,多对多,最大限度消除了数据冗余、修改异常、插入异常、删除异常,基本满足关系规范化的要求。关系数据库中,各个数据表之间关系只能为一对一和一对多的关系。对于多对多的关系必须转换为一对多的关系来处理。设计数据表结构时,应考虑表结构的动态适应性。二、数据库设计的主要步骤需求分析:了解用户的数据需求、处理需求、安全性及完整性要求;概念设计:通过数据抽象,设计系统概念模型,一般为E-R模型;逻辑结构设计:设计系统的模式和外模式,对于关系模型主要是基本表和视图;物理结构设计:设计数据的存储结构和存取方法,如索引的设计;系统实施:组织数据入库、编制应用程序、试运行;运行维护:系统投入运行,长期的维护工作。三、数据库设计需要注意什么3.1、 基础规范3.1.1、使用InnoDB存储引擎支持事务、行级锁、并发性能更好、CPU及内存缓存页优化使得资源利用率更高3.1.2、新库默认使用utf8mb4字符集utf8mb4是utf8的超集,emoji表情以及部分不常见汉字在utf8下会表现为乱码。3.1.3、数据表、数据字段必须加注释添加注释能以后更好的知道是干什么用的3.1.4、禁止使用存储过程、视图、触发器、Event高并发大数据的互联网业务,架构设计思想是“解放数据库CPU,将计算转移到服务层”,并发量大的情况下,这些功能会将数据库拖死,业务逻辑放在服务层具备更好的拓展性,能够轻易实现“增机器就加性能”。数据库擅长存储与索引3.1.5、禁止存储大文件或者大照片大文件和照片存储在文件系统,数据库里存URI更好3.1.6、禁止使用应用程序配置文件内的账号手工访问线上数据库3.1.7、禁止非DBA对线上数据库进行写操作,修改线上数据需要提交工单,由DBA执行,提交的SQL语句必须经过测试3.1.8、分配非DBA以只读账号,必须通过VPN+跳板机访问授权的从库3.1.9、开发、测试、线上环境隔离3.1.10、不在数据库做计算,cpu计算务必移至业务层3.1.11、平衡范式与冗余,为提高效率可以牺牲范式设计,冗余数据3.1.12、拒绝3B,大SQL,大事务,大批量3.2、 命名规范3.2.1、只允许使用内网域名,而不是ip连接数据库使用域名,在切换数据库服务器的时候,只需要更改DNS域名解析,不需要更改配置文件。不只是数据库,缓存的连接,服务的连接都必须使用内网域名。线上环境、开发环境、测试环境数据库内网域名命名规范业务名称:xxx线上环境:dj.xxx.db开发环境:dj.xxx.rdb测试环境:dj.xxx.tdb3.2.2、库名、表名、字段名:小写,下划线风格,不超过32个字符,禁止拼音英文混用3.2.3、表名t_xxx,非唯一索引名idx_xxx,唯一索引名uniq_xxx(idx:索引文件Index file)3.3、 表设计规范3.3.1、单实例表数目必须小于5003.3.2、单表列数目必须小于303.3.3、表必须有主键,例如自增主键3.3.4、禁止使用外键,如果有外键完整性约束,需要应用程序控制外键会导致表与表之间的耦合,update和delete操作都会涉及相关联的表,影响SQL的性能,甚至会造成死锁。高并发情况下容易造成数据库性能,大数据高并发业务场景数据库使用性能优先。3.3.5、控制单表数据量,单表记录控制在千万级。3.4、 字段设计必须规范3.4.1、必须把字段定义为NOT NULL并且提供默认值null的列使索引/索引统计/值都比较复杂,对MySQL来说更难优化null这种类型MySQL内部需要进行特殊处理,增加数据库处理记录的复杂性3.4.2、禁止使用TEXT、BLOB类型会浪费更多的磁盘和空间内存,非必要的大量的大字段查询会淘汰掉热数据,导致内存命中率急剧降低,影响数据库的性能。3.4.3、禁止使用小数存储货币使用整数,小数容易导致钱对不上3.4.4、必须使用varchar(20)存储手机号涉及到区号或者国家的代号手机号会去做数学运算么?varchar可以支持模糊查询 例如:like”138%”3.4.5、禁止使用ENUM,可使用TINYINT代替增加新的ENUM值要做DDL操作ENUM的内部实际存储就是整数,你以为自己定义的是字符串?3.4.6、字段选择类型更小的通常更好:小的数据类型更快,因为它们占用更少的磁盘、内存和CPU缓存,并且处理需要的CPU周期更少。3.5、 索引设计规范3.5.1、单表索引建议控制在5个内3.5.2、单索引字段数不允许草超过5个字段超过5个,实际起不到有效过滤数据的作用3.5.3、禁止在更新十分频繁、区分度不高的属性上建立索引更新会变更B+树,更新频繁的字段建立索引会大大降低数据库性能“性别”这种区分度不大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似3.5.4、建立组合索引,必须把区分度高的字段放在前面MyISAM和INNODB的区别事务安全(MyISAM不支持事务,INNODB支持事务)外键MyISAM不支持外键,INNODB支持外键锁机制(MyISAM是表锁,INNODB是行锁)查询和添加速度(MyISAM批量插入速度快)支持全文索引(MyISAM支持全文索引,INNODB不支持全文索引)MyISAM内存空间使用率比INNODB低3.6、SQL语句优化3.6.1、禁止使用select *,只获取必要的字段,需要显示说明列属性读取不需要的列会增加CPU、IO、NET消耗不能有效的利用覆盖索引使用select *容易在增加或者删除字段后出现程序BUG3.6.2、禁止使用insert into t_xxx values(xxx),必须显示执行插入的列属性容易在增加或者删除字段后出现程序BUG3.6.3、禁止使用属性隐式转换Select uid from t_user where phone=13885236846 会导致全表扫描,而不能命中phone索引3.6.4、禁止在where条件的属性上使用函数或者表达式,在属性上进行计算不能命中索引Select uid from t_user where from_unixtime(day)>=’2017-02-15’会导致全表扫描正确为:select uid from t_user where day>=unix_timestamp(‘2017-02-15 00:00:00’)3.6.5、禁止负向查询,以及%开头的模糊查询负向查询条件:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等,会导致全表扫描%开头的模糊查询,会导致全表扫描3.6.6、禁止大表使用JOIN查询,禁止大表使用子查询会产生临时表,消耗较多的内存与CPU,极大影响数据库性能3.6.7、禁止使用OR条件,必须改为IN查询旧版本的MySQL的OR查询是不能命中索引的,即使能命中索引,为何要让数据库耗费更多的CPU帮助实施查询优化?3.6.8、应用程序必须捕获SQL异常,并有相应的处理3.6.9、负向条件查询不能使用索引Select * from order where status!=0 and status!=1
not in/not exists都不是很好的习惯可以优化为Select * from order where status in(2,3)
3.6.10、前导模糊查询不能用索引Select * from order where desc like ‘%xxx’
而非前导模糊查询则可以:Select * from order where desc like ‘xxx%’
3.6.11、数据区分度不大的字段不宜使用索引能过滤80%数据时就可以使用索引3.6.12、limit高效分页limit越大,效率越低select id from t limit 1000,10 应改为: select id from t where id>1000 limit 103.6.13、如果业务大部分是单条查询,使用Hash索引性能更好3.6.14、允许为null的列,查询有潜在大坑单列索引不存null值,复合索引不存全为null的值,如果列允许为null,可能会得到“不符合预期”的结果集。总结以上是数据设计的基本原则和主要步骤以及设计的时候注意的事项希望对大家有帮助!
无事小神仙
MySQL索引底层实现原理(B树和B+树)
一、B-树索引1. 理论部分数据库索引是存储在磁盘上的,当数据量大时,就不能把整个索引全部加载到内存了,只能逐一加载每一个磁盘块(对应索引树的节点),索引树越低,越矮胖,磁盘IO次数就少MySQL支持两种索引,一种的B-树索引,一种是哈希索引,B-树和哈希表在数据查询时的效率是非常高的。这里我们主要讨论一下MySQL InnoDB存储引擎,基于B-树(但实际上MySQL采用的是B+树结构)的索引结构。B-树是一种m阶平衡树,叶子节点都在同一层,由于每一个节点存储的数据量比较大,所以整个B-树的层数是非常低的,基本上不超过三层。由于磁盘的读取也是按block块操作的(内存是按page页面操作的,一般是16k,是内存页面的整数倍,读1块,刚好放到4个内存页面上),因此B-树的节点大小一般设置为和磁盘块大小一致,这样一个B-树节点,就可以通过一次磁盘I/O把一个磁盘块的数据全部存储下来,所以当使用B-树存储索引的时候,磁盘I/O的操作次数是最少的(MySQL的读写瓶颈,主要集中在磁盘I/O上)数据和索引都是放在磁盘上的,MySQL server得通过操作系统把磁盘上的数据加载到内存中。也就是说,运行起来的进程要访问索引,需要花费磁盘I/O,先把数据、索引读到内存中,而磁盘I/O很影响效率。在C/C++中,如果我们new/malloc向内存申请4个字节,实际上不可能只拿4个字节,内存管理是按页面4K为大小单位的,操作系统相当于批发站,它批发内存是以页面为单位的,我们申请4个字节,实际上我们向内核kernel申请,内核是按页面给我们分配的。按页面分配以后,但是我们的应用程序只需要4个字节,还剩下的字节就被C函数库libc.so或者 libc++.so库的ptmalloc(缓存),tcmalloc来管理,相当于2个函数,负责管理剩下的空闲的字节,等你下次还malloc申请字节的时候,CPU就不用通过中断切换到内核态,直接在用户空间,从C库分配出来就可以了。等用光了,才向内核申请。假设用AVL树存储20000000条数据,需要25层在最坏的情况下,所有的数据都不在一个磁盘块上,读取所有的数据到索引树上,OS需要读取25次磁盘块。我们利用索引读取一条数据,最多需要查找25次2. B树涉及到磁盘到内存的一些读取,我们一般都采用B树结构。B树是平衡树,所有叶子节点都同在一层,B树是m阶平衡树(就是多叉AVL树),一般取值300-500。用B树来存储2千万的索引,假如m取500:最多3层,最多3次磁盘I/O就可以了在真实项目中,由于数据库表的数据数量会有所控制,构建的B+树也都不会超过3层,B树则可能会有4-5层我们在student表中把uid设置为主键,会自动创建索引,当我们进行查询查询操作的时候select * from student where uid=3;
使用索引查找过程:MySQL应用程序一看过滤条件的属性有索引,则先请求存储引擎,然后请求kernel,从磁盘上读uid的索引文件到内存上,然后拿读取的索引的数据构建B树来加速搜索黄色的data表示key索引所在的这一行的数据,data存储的是数据本身内容,还是数据在磁盘上的地址?对于MyISAM而言,*.MYD和*.MYI分别存储数据和索引,即数据和索引分开存放,所以在索引树上存放的只有实际数据在磁盘上的地址,而没有数据本身。对于InnoDB而言,*.idb存放的是数据和索引,数据和索引一起存放, 所以索引树上存放的就是数据本身。这就解释了为什么我们create table的时候不设置主键,InnoDB会自动生成主键,就是因为不建立索引就没有索引树,数据就没有地方存放。关于操作系统从磁盘读取索引文件到内存中的几个问题索引文件在磁盘上存储,磁盘的索引文件中的索引就是已经按B+树构建好的吗?答:那肯定不是,磁盘上只是存储的二进制文件,读取索引文件的时候,在内存上构建一颗B+树存放磁盘上读来的索引数据。数据结构都是在内存上表示的,没有说磁盘上构建个数据结构。操作系统把磁盘的索引文件读到内存上构建B+树,如果磁盘的索引文件太大,内存读不下怎么办?那磁盘IO怎么算次数,现在不是都在内存上的B+树搜索读取数据了吗?答:先读索引文件的前几个字节,里面有第一个要读取的根节点数据在索引文件中的偏移量,读取根节点后,根据你要搜索的数据进行搜索,看是接着加载他的哪个孩子节点。 包括根节点的每一个节点,都存储了索引key值和它的孩子节点在磁盘上的位置偏移量信息。这样每一次搜索,最多只从根节点沿着某个路径加载到叶子节点上,而不可能是整个索引文件都加载到内存在B树和AVL树上搜索一个数据都是O(log2N),为什么还要使用B树?答:我们读取数据总共分为两步:花费磁盘I/O把数据从硬盘读到内存,在内存上构建B树,然后到B树上搜索。虽然在内存上搜索的时间都是O(log2N),但B树高度更低,花费的磁盘I/O更少,速度更快。问题总结:索引文件在磁盘上是二进制的,但是文件中存储了根节点的key值和这个节点的整个的偏移量,还存储的它的左右孩子的key值和整个节点的偏移量。操作系统从磁盘的索引文件中,一次读取一个block块的大小(最好是一个节点大小)到内存中构建B+树,然后在节点中二分搜索元素,如果发现值大于根节点的所有数据值,那么就继续从磁盘的索引文件中把该节点的右孩子节点加载到内存上,然后进行二分搜索查找,以此类推下去。举个搜索的例子select * from student where name = "zhangsan";
如果name没有索引,在MyISAM中(索引和数据分开存放),就是把整张表过1遍,效率非常低。加个limit 1,就是找到第一个符合条件的数据,就会停止查找,效率提高一些如果我们给name建索引,当我们再去执行这个select语句的时候,MySQL server就知道name有索引,直接加载name的索引文件,花费磁盘I/O,把磁盘上的索引(name)加载到内存中来,以二分查找的方式(会比较索引的大小关系)构建成B-树。B树的缺点每个节点中有key(索引),也有data(对于MyISAM来说是数据的地址,对于InnoDB来说是数据本身),但是每一个节点的存储空间是有限的,data占用较大时,会导致每个节点存储的key就会减少(即树的分支变少),同样会导致B树的高度较大,磁盘IO次数花费增大,效率降低!!!三、B+树B+树特点非叶子节点相当于是叶子节点的索引,不存储数据,叶子节点用于存储关键字以及数据。 即每个B+树非叶子节点相对于B树的叶子节点能存储更多的key,这样树的高度就更低,花费的磁盘I/O就更少,查找更快。由于数据全部存在于叶子节点,所以无论查找哪个数据,花费的磁盘I/O次数都是一样的,查找数据耗费的时间比较稳定叶子节点被串在链表中,形成了一个有序链表。做范围搜索和整表遍历的时候直接遍历这个有序链表即可,不用遍历平衡树。MySQL最终为什么要采用B+树存储索引结构?在B树中搜索时,离根节点近的节点找的就快,离根节点远的节点找的就慢,查找数据花费的时间不稳定。B+树所有的数据都在叶子节点,查找数据花费的时间稳定B树的每一个内部节点,都存了key和对应的数据。而B+树的非叶子节点只存关键字,不存数据,B+树的叶子节点存放key和数据。节点的大小是一个块的大小,在节点大小相同的情况下,由于B+树的非叶子节点不存储数据,存储的关键字(key)会远远多于B树,因此,B+树的高度要小于B树,使用的磁盘I/O次数少,查询更快。节点内存在的索引值越多,相邻索引值之间的区间就会越小,查找范围越小,查找的就越容易。而B树非叶子节点相邻索引值之间的区间比B+树要大B树不方便做范围搜索(where age between 10 and 20),整表遍历也不方便。而B+树将所有的叶子节点都串在链表上,做区间搜索以及整表遍历比平衡树快当我们回答问题的时候,不要1 2 3这样把答案背出来,这样效果是很差的,我们回答索引的底层原理的时候可以这样回答:当我们select * from student where name="zhangsan"的时候,数据库引擎会检查name字段有没有索引若没有索引数据库引擎会做整表搜索,效率是比较低的。如果有索引,数据库引擎会向内核申请从磁盘上读取索引文件到内存,一个节点一个节点在内存上构建B+树,一个节点对应一次磁盘I/O。非叶子节点只存关键字key,所有的key和data都会出现在叶子节点,所以用B+树构建索引树,会以最少的磁盘I/O构建出来。其次搜索的时候是以二分的方式进行搜索的,O(log2N)的效率还是很高的。甚至还可以解释一下为什么使用B+树而不使用B树。
无事小神仙
【MySQL】Mysql索引失效场景(15个必知)
背景无论你是技术大佬,还是刚入行的小白,时不时都会踩到Mysql数据库不走索引的坑。常见的现象就是:明明在字段上添加了索引,但却并未生效。前些天就遇到一个稍微特殊的场景,同一条SQL语句,在某些参数下生效,在某些参数下不生效,这是为什么呢?另外,无论是面试或是日常,Mysql索引失效的通常情况都应该了解和学习。为了方便学习和记忆,这篇文件将常见的15种不走索引情况进行汇总,并以实例展示,帮助大家更好地避免踩坑。建议收藏,以备不时之需。数据库及索引准备创建表结构为了逐项验证索引的使用情况,我们先准备一张表t_user:CREATE TABLE `t_user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
`id_no` varchar(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '身份编号',
`username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `union_idx` (`id_no`,`username`,`age`),
KEY `create_time_idx` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
在上述表结构中有三个索引:id:为数据库主键;union_idx:为id_no、username、age构成的联合索引;create_time_idx:是由create_time构成的普通索引;初始化数据初始化数据分两部分:基础数据和批量导入数据。基础数据insert了4条数据,其中第4条数据的创建时间为未来的时间,用于后续特殊场景的验证:INSERT INTO `t_user` (`id`, `id_no`, `username`, `age`, `create_time`) VALUES (null, '1001', 'Tom1', 11, '2022-02-27 09:04:23');
INSERT INTO `t_user` (`id`, `id_no`, `username`, `age`, `create_time`) VALUES (null, '1002', 'Tom2', 12, '2022-02-26 09:04:23');
INSERT INTO `t_user` (`id`, `id_no`, `username`, `age`, `create_time`) VALUES (null, '1003', 'Tom3', 13, '2022-02-25 09:04:23');
INSERT INTO `t_user` (`id`, `id_no`, `username`, `age`, `create_time`) VALUES (null, '1004', 'Tom4', 14, '2023-02-25 09:04:23');
除了基础数据,还有一条存储过程及其调用的SQL,方便批量插入数据,用来验证数据比较多的场景:-- 删除历史存储过程
DROP PROCEDURE IF EXISTS `insert_t_user`
-- 创建存储过程
delimiter $
CREATE PROCEDURE insert_t_user(IN limit_num int)
BEGIN
DECLARE i INT DEFAULT 10;
DECLARE id_no varchar(18) ;
DECLARE username varchar(32) ;
DECLARE age TINYINT DEFAULT 1;
WHILE i < limit_num DO
SET id_no = CONCAT("NO", i);
SET username = CONCAT("Tom",i);
SET age = FLOOR(10 + RAND()*2);
INSERT INTO `t_user` VALUES (NULL, id_no, username, age, NOW());
SET i = i + 1;
END WHILE;
END $
-- 调用存储过程
call insert_t_user(100);
关于存储过程的创建和存储,可暂时不执行,当用到时再执行。数据库版本及执行计划查看当前数据库的版本:select version();
8.0.18
上述为本人测试的数据库版本:8.0.18。当然,以下的所有示例,大家可在其他版本进行执行验证。查看SQL语句执行计划,一般我们都采用explain关键字,通过执行结果来判断索引使用情况。执行示例:explain select * from t_user where id = 1;
执行结果:可以看到上述SQL语句使用了主键索引(PRIMARY),key_len为4;其中key_len的含义为:表示索引使用的字节数,根据这个值可以判断索引的使用情况,特别是在组合索引的时候,判断该索引有多少部分被使用到非常重要。做好以上数据及知识的准备,下面就开始讲解具体索引失效的实例了。1、联合索引不满足最左匹配原则联合索引遵从最左匹配原则,顾名思义,在联合索引中,最左侧的字段优先匹配。因此,在创建联合索引时,where子句中使用最频繁的字段放在组合索引的最左侧。而在查询时,要想让查询条件走索引,则需满足:最左边的字段要出现在查询条件中。实例中,union_idx联合索引组成:KEY `union_idx` (`id_no`,`username`,`age`)
最左边的字段为id_no,一般情况下,只要保证id_no出现在查询条件中,则会走该联合索引。示例一:explain select * from t_user where id_no = '1002';
explain结果:通过explain执行结果可以看出,上述SQL语句走了union_idx这条索引。这里再普及一下key_len的计算:id_no 类型为varchar(18),字符集为utf8mb4_bin,也就是使用4个字节来表示一个完整的UTF-8。此时,key_len = 18* 4 = 72;由于该字段类型varchar为变长数据类型,需要再额外添加2个字节。此时,key_len = 72 + 2 = 74;由于该字段运行为NULL(default NULL),需要再添加1个字节。此时,key_len = 74 + 1 = 75;上面演示了key_len一种情况的计算过程,后续不再进行逐一推演,知道基本组成和原理即可,更多情况大家可自行查看。示例二:explain select * from t_user where id_no = '1002' and username = 'Tom2';
explain结果:很显然,依旧走了union_idx索引,根据上面key_len的分析,大胆猜测,在使用索引时,不仅使用了id_no列,还使用了username列。示例三:explain select * from t_user where id_no = '1002' and age = 12;
explain结果:走了union_idx索引,但跟示例一一样,只用到了id_no列。当然,还有三列都在查询条件中的情况,就不再举例了。上面都是走索引的正向例子,也就是满足最左匹配原则的例子,下面来看看,不满足该原则的反向例子。反向示例:explain select * from t_user where username = 'Tom2' and age = 12;
explain结果:此时,可以看到未走任何索引,也就是说索引失效了。同样的,下面只要没出现最左条件的组合,索引也是失效的:explain select * from t_user where age = 12;
explain select * from t_user where username = 'Tom2';
那么,第一种索引失效的场景就是:在联合索引的场景下,查询条件不满足最左匹配原则。2、 使用了select *在《阿里巴巴开发手册》的ORM映射章节中有一条【强制】的规范:【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。 说明:1)增加查询分析器解析成本。2)增减字段容易与 resultMap 配置不一致。3)无用字段增加网络 消耗,尤其是 text 类型的字段。虽然在规范手册中没有提到索引方面的问题,但禁止使用select * 语句可能会带来的附带好处就是:某些情况下可以走覆盖索引。比如,在上面的联合索引中,如果查询条件是age或username,当使用了select * ,肯定是不会走索引的。但如果希望根据username查询出id_no、username、age这三个结果(均为索引字段),明确查询结果字段,是可以走覆盖索引的:explain select id_no, username, age from t_user where username = 'Tom2';
explain select id_no, username, age from t_user where age = 12;
explain结果:无论查询条件是username还是age,都走了索引,根据key_len可以看出使用了索引的所有列。第二种索引失效场景:在联合索引下,尽量使用明确的查询列来趋向于走覆盖索引;这一条不走索引的情况属于优化项,如果业务场景满足,则进来促使SQL语句走索引。至于阿里巴巴开发手册中的规范,只不过是两者撞到一起了,规范本身并不是为这条索引规则而定的。3 、索引列参与运算直接来看示例:explain select * from t_user where id + 1 = 2 ;
explain结果:可以看到,即便id列有索引,由于进行了计算处理,导致无法正常走索引。针对这种情况,其实不单单是索引的问题,还会增加数据库的计算负担。就以上述SQL语句为例,数据库需要全表扫描出所有的id字段值,然后对其计算,计算之后再与参数值进行比较。如果每次执行都经历上述步骤,性能损耗可想而知。建议的使用方式是:先在内存中进行计算好预期的值,或者在SQL语句条件的右侧进行参数值的计算。针对上述示例的优化如下:-- 内存计算,得知要查询的id为1
explain select * from t_user where id = 1 ;
-- 参数侧计算
explain select * from t_user where id = 2 - 1 ;
第三种索引失效情况:索引列参与了运算,会导致全表扫描,索引失效。4、 索引列参使用了函数示例:explain select * from t_user where SUBSTR(id_no,1,3) = '100';
explain结果:上述示例中,索引列使用了函数(SUBSTR,字符串截取),导致索引失效。此时,索引失效的原因与第三种情况一样,都是因为数据库要先进行全表扫描,获得数据之后再进行截取、计算,导致索引索引失效。同时,还伴随着性能问题。示例中只列举了SUBSTR函数,像CONCAT等类似的函数,也都会出现类似的情况。解决方案可参考第三种场景,可考虑先通过内存计算或其他方式减少数据库来进行内容的处理。第四种索引失效情况:索引列参与了函数处理,会导致全表扫描,索引失效。5、 错误的Like使用示例:explain select * from t_user where id_no like '%00%';
explain结果:针对like的使用非常频繁,但使用不当往往会导致不走索引。常见的like使用方式有:方式一:like ‘%abc’;方式二:like ‘abc%’;方式三:like ‘%abc%’;其中方式一和方式三,由于占位符出现在首部,导致无法走索引。这种情况不做索引的原因很容易理解,索引本身就相当于目录,从左到右逐个排序。而条件的左侧使用了占位符,导致无法按照正常的目录进行匹配,导致索引失效就很正常了。第五种索引失效情况:模糊查询时(like语句),模糊匹配的占位符位于条件的首部。6、 类型隐式转换示例:explain select * from t_user where id_no = 1002;
explain结果:id_no字段类型为varchar,但在SQL语句中使用了int类型,导致全表扫描。出现索引失效的原因是:varchar和int是两个种不同的类型。解决方案就是将参数1002添加上单引号或双引号。第六种索引失效情况:参数类型与字段类型不匹配,导致类型发生了隐式转换,索引失效。这种情况还有一个特例,如果字段类型为int类型,而查询条件添加了单引号或双引号,则Mysql会参数转化为int类型,虽然使用了单引号或双引号:explain select * from t_user where id = '2';
上述语句是依旧会走索引的。7、使用OR操作OR是日常使用最多的操作关键字了,但使用不当,也会导致索引失效。示例:explain select * from t_user where id = 2 or username = 'Tom2';
explain结果:看到上述执行结果是否是很惊奇啊,明明id字段是有索引的,由于使用or关键字,索引竟然失效了。其实,换一个角度来想,如果单独使用username字段作为条件很显然是全表扫描,既然已经进行了全表扫描了,前面id的条件再走一次索引反而是浪费了。所以,在使用or关键字时,切记两个条件都要添加索引,否则会导致索引失效。但如果or两边同时使用“>”和“<”,则索引也会失效:explain select * from t_user where id > 1 or id < 80;
explain结果:第七种索引失效情况:查询条件使用or关键字,其中一个字段没有创建索引,则会导致整个查询语句索引失效; or两边为“>”和“<”范围查询时,索引失效。8、 两列做比较如果两个列数据都有索引,但在查询条件中对两列数据进行了对比操作,则会导致索引失效。这里举个不恰当的示例,比如age小于id这样的两列(真实场景可能是两列同维度的数据比较,这里迁就现有表结构):explain select * from t_user where id > age;
explain结果:这里虽然id有索引,age也可以创建索引,但当两列做比较时,索引还是会失效的。第八种索引失效情况:两列数据做比较,即便两列都创建了索引,索引也会失效。9、 不等于比较示例:explain select * from t_user where id_no <> '1002';
explain结果:当查询条件为字符串时,使用”<>“或”!=“作为条件查询,有可能不走索引,但也不全是。explain select * from t_user where create_time != '2022-02-27 09:56:42';
上述SQL中,由于“2022-02-27 09:56:42”是存储过程在同一秒生成的,大量数据是这个时间。执行之后会发现,当查询结果集占比比较小时,会走索引,占比比较大时不会走索引。此处与结果集与总体的占比有关。需要注意的是:上述语句如果是id进行不等操作,则正常走索引。explain select * from t_user where id != 2;
explain结果:第九种索引失效情况:查询条件使用不等进行比较时,需要慎重,普通索引会查询结果集占比较大时索引会失效。10、 is not null示例:explain select * from t_user where id_no is not null;
explain结果:第十种索引失效情况:查询条件使用is null时正常走索引,使用is not null时,不走索引。11、 not in和not exists在日常中使用比较多的范围查询有in、exists、not in、not exists、between and等。explain select * from t_user where id in (2,3);
explain select * from t_user where id_no in ('1001','1002');
explain select * from t_user u1 where exists (select 1 from t_user u2 where u2.id = 2 and u2.id = u1.id);
explain select * from t_user where id_no between '1002' and '1003';
上述四种语句执行时都会正常走索引,具体的explain结果就不再展示。主要看不走索引的情况:explain select * from t_user where id_no not in('1002' , '1003');
explain结果:当使用not in时,不走索引?把条件列换成主键试试:explain select * from t_user where id not in (2,3);
explain结果:如果是主键,则正常走索引。第十一种索引失效情况:查询条件使用not in时,如果是主键则走索引,如果是普通索引,则索引失效。再来看看not exists:explain select * from t_user u1 where not exists (select 1 from t_user u2 where u2.id = 2 and u2.id = u1.id);
explain结果:当查询条件使用not exists时,不走索引。第十二种索引失效情况:查询条件使用not exists时,索引失效。12、 order by导致索引失效示例:explain select * from t_user order by id_no ;
explain结果:其实这种情况的索引失效很容易理解,毕竟需要对全表数据进行排序处理。那么,添加删limit关键字是否就走索引了呢?explain select * from t_user order by id_no limit 10;
explain结果:结果依旧不走索引。在网络上看到有说如果order by条件满足最左匹配则会正常走索引, 在当前8.0.18版本中并未出现。所以,在基于order by和limit进行使用时,要特别留意。是否走索引不仅涉及到数据库版本,还要看Mysql优化器是如何处理的。这里还有一个特例,就是主键使用order by时,可以正常走索引。explain select * from t_user order by id desc;
explain结果:可以看出针对主键,还是order by可以正常走索引。另外,笔者测试如下SQL语句:explain select id from t_user order by age;
explain select id , username from t_user order by age;
explain select id_no from t_user order by id_no;
上述三条SQL语句都是走索引的,也就是说覆盖索引的场景也是可以正常走索引的。现在将id和id_no组合起来进行order by:explain select * from t_user order by id,id_no desc;
explain select * from t_user order by id,id_no desc limit 10;
explain select * from t_user order by id_no desc,username desc;
explain结果:上述两个SQL语句,都未走索引。第十三种索引失效情况:当查询条件涉及到order by、limit等条件时,是否走索引情况比较复杂,而且与Mysql版本有关,通常普通索引,如果未使用limit,则不会走索引。order by多个索引字段时,可能不会走索引。其他情况,建议在使用时进行expain验证。13、 参数不同导致索引失效此时,如果你还未执行最开始创建的存储过程,建议你先执行一下存储过程,然后执行如下SQL:explain select * from t_user where create_time > '2023-02-24 09:04:23';
其中,时间是未来的时间,确保能够查到数据。explain结果:可以看到,正常走索引。随后,我们将查询条件的参数换个日期:explain select * from t_user where create_time > '2023-11-29 09:04:23';
explain结果:此时,进行了全表扫描。这也是最开始提到的奇怪的现象。为什么同样的查询语句,只是查询的参数值不同,却会出现一个走索引,一个不走索引的情况呢?答案很简单:上述索引失效是因为DBMS发现全表扫描比走索引效率更高,因此就放弃了走索引。也就是说,当Mysql发现通过索引扫描的行记录数超过全表的10%-30%时,优化器可能会放弃走索引,自动变成全表扫描。某些场景下即便强制SQL语句走索引,也同样会失效。类似的问题,在进行范围查询(比如>、< 、>=、<=、in等条件)时往往会出现上述情况,而上面提到的临界值根据场景不同也会有所不同。第十四种索引失效情况:当查询条件为大于等于、in等范围查询时,根据查询结果占全表数据比例的不同,优化器有可能会放弃索引,进行全表扫描。14、 其他当然,还有其他一些是否走索引的规则,这与索引的类型是B-tree索引还是位图索引也有关系,就不再详细展开。这里要说的其他,可以总结为第十五种索引失效的情况:Mysql优化器的其他优化策略,比如优化器认为在某些情况下,全表扫描比走索引快,则它就会放弃索引。针对这种情况,一般不用过多理会,当发现问题时再定点排查即可。小结本篇文章为大家总结了15个常见的索引失效的场景,由于不同的Mysql版本,索引失效策略也有所不同。大多数索引失效情况都是明确的,有少部分索引失效会因Mysql的版本不同而有所不同。因此,建议收藏本文,当在实践的过程中进行对照,如果没办法准确把握,则可直接执行explain进行验证。
无事小神仙
MySQL 意向共享锁、意向排他锁、死锁
一、InnoDB表级锁我们知道,InnoDB是支持行锁,但不是每次都获取行锁,如果不使用索引的,那还是获取的表锁。而且有的时候,我们希望直接去使用表锁在绝大部分情况下都应该使用行锁,因为事务的并发效率比表锁更高,但个别情况下也使用表级锁:事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,给大部分行都加锁(此时不如直接加表锁),不仅这个事务执行效率低,而且可能造成其他事务长时间等待和锁冲突事务涉及多个表,比较复杂,如果都用行锁,很可能引起死锁,造成大量事务回滚当我们希望获取表锁时,可以使用以下命令:LOCK TABLE user READ -- 获取这张表的读锁
LOCK TABLE user WRITE -- 获取这张表的写锁
事务执行...
COMMIT/ROLLBACK; -- 事务提交或者回滚
UNLOCK TABLES; -- 本身自带提交事务,释放线程占用的所有表锁
在使用表锁的时候,涉及到效率的问题:如果我们要获取一张表的排它锁X,最起码得确定,这张表没有被其他事务获取过S锁或X锁,以及这张表没有任何行被其他事务获取过行S或X锁假如这张表有1000万个数据,那我怎么知道这1000万行哪些有行锁哪些没有行锁呢?除了挨个检查,没有更好的办法,这就导致效率低下的问题我们这里学习的意向共享锁和意向排他锁就是用来解决,由于需要加表锁而去挨个遍历数据,确定是否有某些数据被加了行锁,而导致的效率低下问题。作用就是快速判断表里是否有记录被加锁二、意向共享锁和意向排他锁(表锁而非行锁)意向锁的作用:为了可以更快速的获取表锁意向共享锁(IS锁):事务在给一行记录加共享锁前,必须先取得该表的IS锁意向排他锁(IX锁):事务在给一行记录加排他锁前,必须先取得该表的IX锁(上面表格所有的锁都是针对整表)在加行锁之前,由InnoDB存储引擎自动加上表的IS或IX锁,我们无法手动获取IS或IX锁意向锁之间都兼容,不会产生冲突意向锁存在的意义是为了更高效的获取表锁(表格中的X、S、IX、IS指的是表锁,不是行锁)意向锁是表级锁,协调表锁和行锁的共存关系,主要目的是显示事务正在锁定某行或者试图锁定某行。分析事务1获取行X锁和事务2获取表S锁:首先事务1需要给表的第10行数据加X锁,于是InnoDB存储引擎自动给整张表加上了IX锁。当事务2再想获取整张表的S锁时,看到这张表已经有别的事务获取了IX锁了,就说明这张表肯定有某些数据被加上了X锁,这就导致事务2不能给整张表加S锁了。此时事务2只能等待,无法成功获取表S锁当某个事务要获取表的X锁时,不需要再检查哪些行被加上了X或S锁,只需要检查整表是否被加上IX或IS锁即可三、死锁1. 数据库中的死锁MyISAM 表锁是 deadlock free 的, 这是因为 MyISAM 不支持事务,只支持表锁,而且总是一次获得所需的全部锁,要么全部满足,要么等待,因此不会出现死锁。如果是处理多张表,还是可能出现死锁问题的在 InnoDB 中,除单个 SQL 组成的事务外,锁是逐步获得的,即锁的粒度比较小(行锁),这就决定了在 InnoDB 中发生死锁是可能的死锁问题一般都是我们自己的应用造成的,和多线程编程的死锁情况相似,大部分都是由于我们多个线程在获取多个锁资源的时候,获取的顺序不同而导致的死锁问题。因此我们应用在对数据库的多个表做更新的时候,不同的代码段,应对这些表按相同的顺序进行更新操作,以防止锁冲突导致死锁问题2. 死锁场景以及解决办法死锁出现的场景如下:事务1成功获取行锁1事务2成功获取行锁2…事务1无法获取行锁2,被阻塞的同时也无法释放行锁1事务2无法获取行锁1,被阻塞的同时也无法释放行锁2此时所有的事务都阻塞住了,相当于进程内的所有线程都阻塞住了,发生了死锁问题解决死锁办法:多个事务/线程获取多个相同资源锁的时候应该按照同样的顺序获取锁。与此同时,由于mysqld(MySQL Server守护进程)设置了事务阻塞的超时时间,事务不会阻塞很长时间,超时后事务处理失败,自动释放当前占有的锁3. 操作设置自动提交 以及 可重复读隔离级别,开启事务查询一下表数据,在可重复读隔离级别使用的是MVCC提供的快照读,并没有加锁事务1获取id=7的排他锁,事务2获取id=8的排他锁事务1再次获取id=8的排他锁,发生阻塞事务2再次获取id=7的排他锁此时由于MySQL Server检测到发生了死锁,于是解除事务1的阻塞,进行事务1的rollback,释放其占有的行锁,于是事务2成功获取id=7的排他锁两个事务发生死锁时,MySQL Server会选择一个事务释放锁并进行rollback四、锁的优化建议在能正确完成业务的前提下,为确保效率,尽量使用较低的隔离级别(必须避免脏读)设计合理的索引并尽量使用索引访问数据,使加锁更准确,减少锁冲突的机会,提高并发能力选择合理的事务大小,小事务发生锁冲突的概率小(事务越大,包含的SQL越多,可能包含更多的表资源和行资源的锁,增大了锁冲突的概率)不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大大减少死锁的机会尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响(其实等值查询也会加间隙锁)不要申请超过实际需要的锁级别除非必须,查询时不要显示加锁(在已提交读和可重复读隔离级别,MVCC提供了读取机制,不需要手动加锁)
无事小神仙
MySQL的事务原理和实现?
MySQL事务的底层实现原理特点ACID:原子性(Atomicity)一致性(Consistency)隔离型(Isolation)持久性(Durability)一、事务的目的可靠性和并发处理可靠性:数据库要保证当insert或update操作时抛异常或者数据库crash的时候需要保障数据的操作前后的一致,想要做到这个,我需要知道我修改之前和修改之后的状态,所以就有了undo log和redo log。并发处理:也就是说当多个并发请求过来,并且其中有一个请求是对数据修改操作的时候会有影响,为了避免读到脏数据,所以需要对事务之间的读写进行隔离,至于隔离到啥程度得看业务系统的场景了,实现这个就得用MySQL 的隔离级别。二、实现事务功能的三个技术日志文件(redo log 和 undo log)锁技术MVCC2.1 redo log 与 undo log介绍2.1.1 redo log什么是redo log ? redo log叫做重做日志,是用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。 当事务提交之后会把所有修改信息都会存到该日志中。假设有个表叫做tb1(id,username) 现在要插入数据(3,ceshi)start transaction;
select balance from bank where name="zhangsan";
// 生成 重做日志 balance=600
update bank set balance = balance - 400;
// 生成 重做日志 amount=400
update finance set amount = amount + 400;
commit
redo log 有什么作用?mysql 为了提升性能不会把每次的修改都实时同步到磁盘,而是会先存到Boffer Pool(缓冲池)里头,把这个当作缓存来用。然后使用后台线程去做缓冲池和磁盘之间的同步。那么问题来了,如果还没来的同步的时候宕机或断电了怎么办?这样会导致丢部分已提交事务的修改信息!所以引入了redo log来记录已成功提交事务的修改信息,并且会把redo log持久化到磁盘,系统重启之后在读取redo log恢复最新数据。总结redo log是用来恢复数据的 用于保障已提交事务的持久化特性。2.1.2undo log什么是 undo log ? undo log 叫做回滚日志,用于记录数据被修改前的信息。他正好跟前面所说的重做日志所记录的相反,重做日志记录数据被修改后的信息。undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。 每次写入数据或者修改数据之前都会把修改前的信息记录到 undo log。undo log 有什么作用? undo log 记录事务修改之前版本的数据信息,因此假如由于系统错误或者rollback操作而回滚的话可以根据undo log的信息来进行回滚到没被修改前的状态。总结 undo log是用来回滚数据的用于保障 未提交事务的原子性2.2 mysql锁技术2.2.1 mysql锁技术当有多个请求来读取表中的数据时可以不采取任何操作,但是多个请求里有读请求,又有修改请求时必须有一种措施来进行并发控制。不然很有可能会造成不一致。读写锁解决上述问题很简单,只需用两种锁的组合来对读写请求进行控制即可,这两种锁被称为:共享锁(shared lock),又叫做"读锁"读锁是可以共享的,或者说多个读请求可以共享一把锁读数据,不会造成阻塞。排他锁(exclusive lock),又叫做"写锁"写锁会排斥其他所有获取锁的请求,一直阻塞,直到写入完成释放锁。总结 通过读写锁,可以做到读读可以并行,但是不能做到写读,写写并行,事务的隔离性就是根据读写锁来实现的。2.3 MVCC基础MVCC (MultiVersion Concurrency Control) 叫做多版本并发控制。InnoDB的 MVCC ,是通过在每行记录的后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存了行的过期时间,当然存储的并不是实际的时间值,而是系统版本号他的主要实现思想是通过数据多版本来做到读写分离。从而实现不加锁读进而做到读写并行.。MVCC在mysql中的实现依赖的是undo log与read viewundo log :undo log 中记录某行数据的多个版本的数据。read view :用来判断当前版本数据的可见性三、事务的实现事务的原子性是通过undolog来实现的事务的持久性性是通过redolog来实现的事务的隔离性是通过(读写锁+MVCC)来实现的事务的终极大 boss 一致性是通过原子性,持久性,隔离性来实现的!!!原子性,持久性,隔离性的目的也是为了保障数据的一致性!总之,ACID只是个概念,事务最终目的是要保障数据的可靠性,一致性。3.1 原子性的实现什么是原子性:一个事务必须被视为不可分割的最小工作单位,一个事务中的所有操作要么全部成功提交,要么全部失败回滚,对于一个事务来说不可能只执行其中的部分操作,这就是事务的原子性。以上概念相信大家伙儿都了解,那么数据库是怎么实现的呢?就是通过回滚操作。所谓回滚操作就是当发生错误异常或者显式的执行rollback语句时需要把数据还原到原先的模样,所以这时候就需要用到undo log来进行回滚,接下来看一下undo log在实现事务原子性时怎么发挥作用的3.1.1 undo log 的生成假设有两个表 bank和finance,当进行插入,删除以及更新操作时生成的undo log。从上图可以了解到数据的变更都伴随着回滚日志的产生:产生了被修改前数据(zhangsan,1000) 的回滚日志产生了被修改前数据(zhangsan,0) 的回滚日志根据上面流程可以得出如下结论:每条数据变更(insert/update/delete)操作都伴随一条undo log的生成,并且回滚日志必须先于数据持久化到磁盘上所谓的回滚就是根据回滚日志做逆向操作,比如delete的逆向操作为insert,insert的逆向操作为delete,update的逆向为update等。3.1.2 根据undo log 进行回滚为了做到同时成功或者失败,当系统发生错误或者执行rollback操作时需要根据undo log 进行回滚img回滚操作就是要还原到原来的状态,undo log记录了数据被修改前的信息以及新增和被删除的数据信息,根据undo log生成回滚语句,比如:(1) 如果在回滚日志里有新增数据记录,则生成删除该条的语句(2) 如果在回滚日志里有删除数据记录,则生成生成该条的语句(3) 如果在回滚日志里有修改数据记录,则生成修改到原先数据的语句3.2 持久性的实现事务一旦提交,其所做的修改会永久保存到数据库中,此时即使系统崩溃修改的数据也不会丢失。先了解一下MySQL的数据存储机制,MySQL的表数据是存放在磁盘上的,因此想要存取的时候都要经历磁盘IO,然而即使是使用SSD磁盘IO也是非常消耗性能的。为此,为了提升性能InnoDB提供了缓冲池(Buffer Pool),Buffer Pool中包含了磁盘数据页的映射,可以当做缓存来使用:读数据:会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取再放入缓冲池;写数据:会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中;上面这种缓冲池的措施虽然在性能方面带来了质的飞跃,但是它也带来了新的问题,当MySQL系统宕机,断电的时候可能会丢数据!!!因为我们的数据已经提交了,但此时是在缓冲池里头,还没来得及在磁盘持久化,所以我们急需一种机制需要存一下已提交事务的数据,为恢复数据使用。于是 redo log就派上用场了。下面看下redo log是什么时候产生的既然redo log也需要存储,也涉及磁盘IO为啥还用它?(1)redo log 的存储是顺序存储,而缓存同步是随机操作。(2)缓存同步是以数据页为单位的,每次传输的数据大小大于redo log。3.3 隔离性实现隔离性是事务ACID特性里最复杂的一个。在SQL标准里定义了四种隔离级别,每一种级别都规定一个事务中的修改,哪些是事务之间可见的,哪些是不可见的。级别越低的隔离级别可以执行越高的并发,但同时实现复杂度以及开销也越大。MySQL隔离级别有以下四种(级别由低到高):READUNCOMMITED(未提交读)READCOMMITED(提交读)REPEATABLEREAD(可重复读)SERIALIZABLE (可重复读)只要彻底理解了隔离级别以及他的实现原理就相当于理解了ACID里的隔离型。前面说过原子性,隔离性,持久性的目的都是为了要做到一致性,但隔离型跟其他两个有所区别,原子性和持久性是为了要实现数据的可性保障靠,比如要做到宕机后的恢复,以及错误后的回滚。那么隔离性是要做到什么呢? 隔离性是要管理多个并发读写请求的访问顺序。 这种顺序包括串行或者是并行 说明一点,写请求不仅仅是指insert操作,又包括update操作。总之,从隔离性的实现可以看出这是一场数据的可靠性与性能之间的权衡:可靠性性高的,并发性能低(比如Serializable)。可靠性低的,并发性能高(比如 Read Uncommited)3.3.1 READ UNCOMMITTED在READ UNCOMMITTED隔离级别下,事务中的修改即使还没提交,对其他事务是可见的。事务可以读取未提交的数据,造成脏读。因为读不会加任何锁,所以写操作在读的过程中修改数据,所以会造成脏读。好处是可以提升并发处理性能,能做到读写并行。换句话说,读的操作不能排斥写请求。优点:读写并行,性能高缺点:造成脏读3.3.2 READ COMMITTED一个事务的修改在他提交之前的所有修改,对其他事务都是不可见的。其他事务能读到已提交的修改变化。在很多场景下这种逻辑是可以接受的。InnoDB在 READ COMMITTED,使用排它锁,读取数据不加锁而是使用了MVCC机制。或者换句话说他采用了读写分离机制。但是该级别会产生不可重读以及幻读问题。什么是不可重读?在一个事务内多次读取的结果不一样。为什么会产生不可重复读?这跟 READ COMMITTED 级别下的MVCC机制有关系,在该隔离级别下每次 select的时候新生成一个版本号,所以每次select的时候读的不是一个副本而是不同的副本。在每次select之间有其他事务更新了我们读取的数据并提交了,那就出现了不可重复读3.3.3 REPEATABLE READ(Mysql默认隔离级别)在一个事务内的多次读取的结果是一样的。这种级别下可以避免,脏读,不可重复读等查询问题。mysql 有两种机制可以达到这种隔离级别的效果,分别是采用读写锁以及MVCC。采用读写锁实现:为什么能可重复读?只要没释放读锁,在次读的时候还是可以读到第一次读的数据。优点:实现起来简单缺点:无法做到读写并行采用MVCC实现:为什么能可重复读?因为多次读取只生成一个版本,读到的自然是相同数据。优点:读写并行缺点:实现的复杂度高但是在该隔离级别下仍会存在幻读的问题,关于幻读的解决我打算另开一篇来介绍。3.3.4 SERIALIZABLE该隔离级别理解起来最简单,实现也最简单。在隔离级别下除了不会造成数据不一致问题,没其他优点。3.4 一致性的实现数据库总是从一个一致性的状态转移到另一个一致性的状态。下面举个例子,zhangsan 从银行卡转400到理财账户:
start transaction;
select balance from bank where name="zhangsan";
// 生成 重做日志 balance=600
update bank set balance = balance - 400;
// 生成 重做日志 amount=400
update finance set amount = amount + 400;
commit;
假如执行完 update bank set balance = balance - 400;之发生异常了,银行卡的钱也不能平白无故的减少,而是回滚到最初状态。又或者事务提交之后,缓冲池还没同步到磁盘的时候宕机了,这也是不能接受的,应该在重启的时候恢复并持久化。假如有并发事务请求的时候也应该做好事务之间的可见性问题,避免造成脏读,不可重复读,幻读等。在涉及并发的情况下往往在性能和一致性之间做平衡,做一定的取舍,所以隔离性也是对一致性的一种破坏。总结实现事务采取了哪些技术以及思想?原子性:使用 undo log ,从而达到回滚持久性:使用 redo log,从而达到故障后恢复隔离性:使用锁以及MVCC,运用的优化思想有读写分离,读读并行,读写并行一致性:通过回滚,以及恢复,和在并发环境下的隔离做到一致性。
无事小神仙
MySQL表锁、行锁、排它锁和共享锁
事务隔离级别的实现原理:简单来说就是各种锁机制和MVCC多版本并发控制我们学习知识的时候,需要了解知识点出现的原因,什么情况下能用到这个知识我们说到事务,就得说到事务的ACID特性,为什么需要隔离性呢?因为事务要能够允许并发执行,并发执行为了同时保证数据的安全性,一致性和并发的效率,就需要设置事务的隔离级别一、事务隔离机制的选择如果我们完全不管,使用未提交读的事务隔离机制,任由这些线程并发操作数据库,那就会出现脏读(读取了未commit的数据)、不可重复读(两次查询值不同)、幻读(两次查询数据量不同)等问题,数据的安全性最低,优点是并发效率非常高,一般不会使用如果我们串行化(靠锁实现),通过锁给所有的事务都排个序,虽然数据的安全性提高了,并发的效率就太低了,一般也不会使用所以我们一般用的是已提交读、可重复读这两个隔离级别,平衡了数据的安全性,一致性以及并发的效率 ,是由MVCC多版本并发控制实现的(MVCC是已提交读和可重复读的原理,锁是串行化的原理)二、表级锁&行级锁表级锁:对整张表加锁。开销小(因为不用去找表的某一行的记录进行加锁,要修改这张表,直接申请加这张表的锁),加锁快,不会出现死锁;锁粒度大,发生锁冲突的概率高,并发度低行级锁:对某行记录加锁。开销大(需要找到表中相应的记录,有搜表搜索引的过程),加锁慢,会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度高InnoDB存储引擎支持事务处理,表支持行级锁定,并发能力更好InnoDB行锁是通过给索引上的索引项加锁来实现的,而不是给表的行记录加锁实现的,这就意味者只有通过索引条件检索数据,InnoDB才使用行级锁,否则InnoDB将使用表锁由于InnoDB的行锁实现是针对索引字段添加的锁,不是针对行记录加的锁,因此虽然访问的是InnoDB引擎下表的不同行,但如果使用相同的索引字段作为过滤条件,依然会发生锁冲突,只能串行进行,不能并发进行即使SQL中使用了索引,但是经过MySQL的优化器后,如果认为全表扫描比使用索引效率高,此时会放弃使用索引,因此也不会使用行锁,而是使用表锁,比如对一些很小的表,MySQL就不会去使用索引三、排它锁(Exclusive)和共享锁(Shared)排它锁,又称为X锁,写锁共享锁,又称为S锁,读锁读读(SS)之间是可以兼容的,但是读写(SX)之间,写写(XX)之间是互斥的对事务加X和S锁之间有以下的关系:一个事务对数据对象A加了 S 锁,可以对A进行读取操作但不能进行update操作,加锁期间其它事务能对A加S锁但不能加 X 锁一个事务对数据对象A加了 X 锁,就可以对A进行读取和更新,加锁期间其它事务不能对A加任何锁显式加锁:select … lock in share mode强制获取共享锁,select … for update获取排它锁1. 测试不同事务之间排它锁和共享锁的兼容性我们先查看表的SQL以及内容查看隔离级别:首先开启一个事务,给id=7的数据加上排它锁在用另一个客户端开启事务我们用另一个事务的服务线程给id=7的数据加上排它锁,阻塞了我们尝试给id=7的数据加上共享锁,还是阻塞了再获取id=8的共享锁和排它锁但是可以成功获取id=8的共享锁和排它锁总结:不同事务之间对于数据的锁,只有SS锁可以共存,XX、SX、XS都不能共存2. 测试行锁加在索引项上其实行锁是加在索引树上的事务1用表的无索引字段name作为过滤条件事务2现在同样想获取这条记录的排它锁,可想而知地失败了;那现在事务2获取不同行chenwei的记录的排它锁,试试能不能成功事务2获取不同行chenwei的记录的排它锁,同样失败了InnoDB是支持行锁的,刚才以主键id为过滤条件时,事务1和事务2获取不同行的锁是可以成功的。然而现在我们发现获取name为chenwei的排它锁也获取不到了,这是为什么?我们解释一下:InnoDB的行锁是通过给索引项加锁来实现的,而不是给表的行记录加锁实现的而我们用name作为过滤条件没有用到索引,自然就不会使用行锁,而是使用表锁。这就意味着只有通过索引检索数据,InnoDB才使用行级锁,如果做整表扫描,InnoDB将使用表锁!!!我们给name字段加上索引添加索引,开启事务后,重新获取不同行的排它锁我们发现,给name加上索引后,两个事务可以获取到不同行的排它锁(for update),再一次证明了InnoDB的行锁是加在索引项上的因为现在name走的是索引, 通过zhangsan在辅助索引树上找到它所在行记录的id是7,然后到主键索引树上,获取对应行记录的排他锁(MySQL Server会根据情况,在主键索引树和辅助索引树上加锁)四、串行化隔离级别测试在SERIALIZABLE隔离级别下,所有的事务都自动使用排它锁或共享锁,不需要用户手动加锁(for in share mode/for update)设置串行化隔离级别两个事务可以同时获取共享锁(SS共存)现在让事务2插入数据此时由于insert需要加排它锁,但由于事务1已经对整张表添加了共享锁,事务2无法再对表成功加锁(SX不共存)rollback一下因为我们给name加上了索引,以上的select相当于给name为zhangsan的数据加上了行共享锁事务2 update事务2不能update,因为此时已经被事务1的共享锁锁住了id=7 name=zhangsan这条记录的索引项事务2在辅助索引树上找zhangsan,找到对应的主键值,然后去主键索引树找到相应的记录,但是发现这行记录已经被共享锁锁住了,事务2可以获取共享锁,但是不能获取排他锁我们用主键索引id试试能不能update依然阻塞住了,虽然我们where后面的字段现在使用的id而不是name,但是name也是通过辅助索引树找到对应的主键,再到主键索引树上找相应的记录,而主键索引树上的记录加了锁(MySQL Server会根据情况,在主键索引树和辅助索引树上加锁)我们update id=8的数据,成功了。因为我们select的时候,只是给id=7 name=zhangsan的数据加上了行锁,我们操作id=8的数据当然可以成功有索引,则使用行锁;没有索引,则使用表锁。表级锁还是行级锁说的是锁的粒度,共享锁和排他锁说的是锁的性质,不管是表锁还是行锁,都有共享锁和排他锁的区分。
无事小神仙
MySQL主从同步延迟原因与解决方案
一、MySQL数据库主从同步延迟产生的原因MySQL的主从复制都是单线程的操作,主库对所有DDL和DML产生的日志写进binlog,由于binlog是顺序写,所以效率很高。Slave的SQL Thread线程将主库的DDL和DML操作事件在slave中重放。DML和DDL的IO操作是随即的,不是顺序的,成本高很多。另一方面,由于SQL Thread也是单线程的,当主库的并发较高时,产生的DML数量超过slave的SQL Thread所能处理的速度,或者当slave中有大型query语句产生了锁等待那么延时就产生了。常见原因:Master负载过高、Slave负载过高、网络延迟、机器性能太低、MySQL配置不合理。二、关于DDL和DMLSQL语言共分为以下几大类:查询语言DQL,控制语言DCL,操纵语言DML,定义语言DDL。事务控制TCLDQL(Data QUERY Languages)语句:即数据库定义语句,用来查询SELECT子句,FROM子句,WHERE子句组成的查询块,比如:select–from–where–grouop by–having–order by–limitDDL(Data Definition Languages)语句:即数据库定义语句,用来创建数据库中的表、索引、视图、存储过程、触发器等,常用的语句关键字有:CREATE,ALTER,DROP,TRUNCATE,COMMENT,RENAME。增删改表的结构DML(Data Manipulation Language)语句:即数据操纵语句,用来查询、添加、更新、删除等,常用的语句关键字有:SELECT,INSERT,UPDATE,DELETE,MERGE,CALL,EXPLAIN PLAN,LOCK TABLE,包括通用性的增删改查。增删改表的数据DCL(Data Control Language)语句:即数据控制语句,用于授权/撤销数据库及其字段的权限(DCL is short name of Data Control Language which includes commands such as GRANT and mostly concerned with rights, permissions and other controls of the database system.)。常用的语句关键字有:GRANT,REVOKE。TCL(Transaction Control Language)语句:事务控制语句,用于控制事务,常用的语句关键字有:COMMIT,ROLLBACK,SAVEPOINT,SET TRANSACTION。三、主从延时排查方法通过监控 show slave status 命令输出的Seconds_Behind_Master参数的值来判断:NULL,表示io_thread或是sql_thread有任何一个发生故障;0,该值为零,表示主从复制良好;正值,表示主从已经出现延时,数字越大表示从库延迟越严重四、解决方案解决数据丢失的问题:半同步复制 从MySQL5.5开始,MySQL已经支持半同步复制了,半同步复制介于异步复制和同步复制之间,主库在执行完事务后不立刻返回结果给客户端,需要等待至少一个从库接收到并写到relay log中才返回结果给客户端。相对于异步复制,半同步复制提高了数据的安全性,同时它也造成了一个TCP/IP往返耗时的延迟。主库配置sync_binlog=1,innodb_flush_log_at_trx_commit=1 sync_binlog的默认值是0,MySQL不会将binlog同步到磁盘,其值表示每写多少binlog同步一次磁盘。 innodb_flush_log_at_trx_commit为1表示每一次事务提交或事务外的指令都需要把日志flush到磁盘。注意:将以上两个值同时设置为1时,写入性能会受到一定限制,只有对数据安全性要求很高的场景才建议使用,比如涉及到钱的订单支付业务,而且系统I/O能力必须可以支撑!3.1 解决从库复制延迟的问题:架构方面 业务的持久化层的实现采用分库架构,mysql服务可平行扩展,分散压力。 单个库读写分离,一主多从,主写从读,分散压力。这样从库压力比主库高,保护主库。 服务的基础架构在业务和mysql之间加入memcache或者redis的cache层。降低mysql的读压力。 不同业务的mysql物理上放在不同机器,分散压力。 使用比主库更好的硬件设备作为slave,mysql压力小,延迟自然会变小。硬件方面 采用好服务器,比如4u比2u性能明显好,2u比1u性能明显好。 存储用ssd或者盘阵或者san,提升随机写的性能。 主从间保证处在同一个交换机下面,并且是万兆环境。总结,硬件强劲,延迟自然会变小。一句话,缩小延迟的解决方案就是花钱和花时间。mysql主从同步加速 sync_binlog在slave端设置为0 –logs-slave-updates 从服务器从主服务器接收到的更新不记入它的二进制日志。 直接禁用slave端的binlog .slave端,如果使用的存储引擎是innodb,innodb_flush_log_at_trx_commit =2从文件系统本身属性角度优化 master端修改linux、Unix文件系统中文件的etime属性, 由于每当读文件时OS都会将读取操作发生的时间回写到磁盘上,对于读操作频繁的数据库文件来说这是没必要的,只会增加磁盘系统的负担影响I/O性能。可以通过设置文件系统的mount属性,组织操作系统写atime信息,在linux上的操作为:打开/etc/fstab,加上noatime参数/dev/sdb1 /data reiserfs noatime 1 2然后重新mount文件系统#mount -oremount /data同步参数调整主库是写,对数据安全性较高,比如sync_binlog=1, innodb_flush_log_at_trx_commit = 1 之类的设置是需要的而slave则不需要这么高的数据安全,完全可以讲sync_binlog设置为0或者关闭binlog,innodb_flushlog也可以设置为0来提高sql的执行效率3.2 MySql数据库从库同步其他问题及解决方案mysql主从复制存在的问题: 主库宕机后,数据可能丢失 从库只有一个sql Thread,主库写压力大,复制很可能延时解决方法: 半同步复制—解决数据丢失的问题 并行复制----解决从库复制延迟的问题半同步复制mysql semi-sync(半同步复制)半同步复制: 5.5集成到mysql,以插件的形式存在,需要单独安装 确保事务提交后binlog至少传输到一个从库 不保证从库应用完这个事务的binlog 性能有一定的降低,响应时间会更长 网络异常或从库宕机,卡主主库,直到超时或从库恢复主从复制–异步复制原理、半同步复制和并行复制原理比较
无事小神仙
从执行计划了解MySQL优化策略
前言在MySQL中,执行计划是优化器根据查询语句生成的一种重要的数据结构,它描述了如何通过组合底层操作实现查询的逻辑。当我们编写一条SQL语句时,MySQL会自动对其进行优化,并生成最优的执行计划以实现更快的查询速度。各位精通MySQL的大佬们,像往常一样,我们经常会遇见一堆SQL查询要处理。作为一个优秀的MySQL的操盘手,不能让这些SQL语句任性地在数据库中胡乱扔,必须要好好管管它们!但是,面对复杂的SQL语句和复杂的数据库架构,我们如何才能快速和准确地分析查询性能?难道要求神通广大的数据库教父出山帮忙?不要担心!在MySQL世界中,EXPLAIN命令就像是一个小巧而灵活的工具,能够帮助你轻松解决这些问题。使用它,并结合我们自己的经验和智慧,我们就可以像成功解锁小学奥数一样地优化SQL查询,成为MySQL中的一名优秀演员。废话不多说,现在就让我们开始吧,让我们去通过熟悉每个查询的执行过程,并用一颗豁达的心态理解每个SQL查询的优化分析!本文将介绍MySQL执行计划的相关知识。首先我们将介绍执行计划的概念以及MySQL优化器是如何生成执行计划的,然后我们将深入探讨执行计划中各种类型的操作符,最后我们将讨论如何通过执行计划来诊断性能问题。一、什么是执行计划执行计划是MySQL优化器为了优化查询而生成的一种数据结构,它记录了数据库系统执行查询时所采取的操作流程,即对查询语句的各部分如何进行处理以最终得到查询结果的过程。执行计划通常被表示为一棵树状结构,节点代表不同的操作符(operator),叶子节点代表访问底层数据的方式,例如表扫描或索引查找等。获取MySQL查询执行计划的方法有多种,下面介绍两种常用的方法:1.1. 使用EXPLAIN命令EXPLAIN命令可以帮助我们分析查询的执行计划,帮助我们发现潜在的性能问题。我们可以通过以下命令来使用:EXPLAIN SELECT *
FROM employees
WHERE salary > 50000;
执行以上命令后,MySQL将返回一张表格,其中包含了查询语句所使用的索引、扫描的行数以及各个步骤的 cost 等信息。1.2. 使用PROFILINGMySQL提供了一个 PROFILING 工具,能够记录 MySQL 服务器上连接和查询的操作时间,包括每个 SQL 语句执行的时间以及资源的消耗。我们可以通过以下命令来启用 PROFILING:SET profiling = 1;
然后运行我们要分析的查询语句。最后,我们可以查看查询的 profile 信息,例如:SHOW PROFILE ALL FOR QUERY n;
其中 n 表示查询语句的 ID,它可以通过如下命令获取:SHOW PROFILES;
以上就是获取MySQL执行计划的两种常用方法,你需要根据具体情况选择不同的方法来获取和分析执行计划。二、执行计划生成过程在查询过程中,MySQL优化器必须决定查询的最佳执行计划。这个过程通常称为查询优化。查询优化的目标是选择最小代价的执行计划,也就是要在所有可能的执行计划中选择一个最快的执行计划。查询优化可以看作是一个搜索空间的问题,其中搜索空间包括所有可能的执行计划。以下是MySQL优化器确定执行计划的一般流程:解析SQL语句并构造语法树。MySQL首先解析SQL语句,并使用语法树表示查询。语法树由各种操作符和表达式组成。例如,下面是一个查询语句的语法树: SELECT *
FROM employees
WHERE salary > 50000;
生成所有可能的执行计划。接下来,MySQL优化器将生成所有可能的执行计划。它会尝试所有可能的操作顺序和访问方法,以找到最优的执行计划。估算每个执行计划的代价。对于每个执行计划,MySQL会估算其代价并选择代价最小的执行计划。代价通常由磁盘I/O,内存使用等因素组成。执行查询。最后,MySQL执行计划并返回结果。三、执行计划的操作符MySQL执行计划中的操作符分为三大类:查询计划操作符、连接操作符和辅助操作符。下面我们将分别介绍这三种操作符。3.1. 查询计划操作符查询计划操作符包括以下类型:表扫描(Table scan):这是一种简单的操作,它通过遍历整个表来检索记录。当表没有索引或索引不能用于查询时,MySQL就会采用这种方式。查询示例:SELECT *
FROM employees;
索引查找(Index lookup):当查询条件中包含索引列或者覆盖索引时,MySQL会使用索引查找操作。这个操作通常比表扫描快很多。查询示例:SELECT *
FROM employees
WHERE emp_id = 1001;
全文搜索(Fulltext search):当需要在全文中搜索某个关键字时,MySQL会采用全文搜索操作。但是它要求表必须有全文索引。查询示例:SELECT *
FROM articles
WHERE MATCH(title, body) AGAINST ('MySQL');
排序(Sort):当查询需要按照指定的排序规则显示结果时,MySQL采用排序操作。查询示例:SELECT *
FROM employees
ORDER BY salary DESC;
分组(Group):当需要对结果集分组时,MySQL会采用分组操作符。查询示例:SELECT department, AVG(salary)
FROM employees
GROUP BY department;
聚合(Aggregation):当需要对整个表或者某一部分进行聚合操作时,MySQL采用聚合操作符。查询示例:SELECT COUNT(*)
FROM employees;
3.2. 连接操作符连接操作符用于将不同数据源的数据进行连接。MySQL支持以下几种连接操作符:等值连接(Equal join):当两个表中包含相同的关键字(也就是外键)时,MySQL会使用等值连接操作符实现连接操作。查询示例:SELECT *
FROM employees JOIN departments
ON employees.department_id = departments.department_id;
非等值连接(Non-equal join):当连接条件使用非等于运算符时(如>、<、BETWEEN等),MySQL会使用非等值连接操作符。查询示例:SELECT *
FROM employees JOIN salaries
ON employees.emp_id = salaries.emp_id AND salaries.salary > 50000;
自连接(Self join):也称为自连接操作,它将一个表与其自身进行连接。查询示例:SELECT a.emp_name AS name1, b.emp_name AS name2
FROM employees a, employees b
WHERE a.manager_id = b.emp_id;
外连接(Outer join):当我们需要查询两个表的记录时,无论这两个表是否存在相同的关键字,我们都可以使用外连接操作符进行连接。查询示例:SELECT *
FROM employees LEFT JOIN salaries
ON employees.emp_id = salaries.emp_id;
3.3. 辅助操作符辅助操作符是MySQL执行计划中的其他操作。这些操作通常用于优化性能,包括以下几种类型:索引合并(Index merge):当查询涉及多个索引时,MySQL会将这些索引进行合并来提高性能。查询示例:SELECT *
FROM employees
WHERE emp_id = 1001 AND salary > 50000;
子查询(Subquery):当一个查询需要嵌套在另一个查询之内时,MySQL会使用子查询操作符。查询示例:SELECT *
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
临时表(Temporary table):当查询涉及到大量数据或者存在复杂的连接关系时,MySQL会在磁盘上创建一个临时表来处理查询。查询示例:SELECT *
FROM employees JOIN salaries JOIN departments
ON employees.emp_id = salaries.emp_id AND employees.department_id = departments.department_id;
以上就是MySQL执行计划中各种操作符的介绍。这些操作符在执行计划生成和性能优化中都会扮演重要的角色。四、执行计划的诊断分析当我们发现MySQL的性能有问题时,我们可以通过执行计划来诊断性能问题。下面是一些常用的技巧:4.1. 使用EXPLAIN命令EXPLAIN命令可以帮助我们分析查询的执行计划,帮助我们发现潜在的性能问题。我们可以通过以下命令来使用:EXPLAIN SELECT *
FROM employees
WHERE salary > 50000;
输出结果将告诉我们MySQL所采取的操作顺序和访问方式以及每个操作的代价。4.2. 检查索引在许多情况下,我们可以通过添加适当的索引来提高查询性能。我们可以使用以下命令检查表上的索引:SHOW INDEXES FROM employees;
输出结果将告诉我们索引的名称,类型,所包含的列等信息。4.3. 分析查询日志我们可以通过查看MySQL的查询日志来分析查询的执行情况。我们可以使用以下命令启用查询日志:ini复制代码SET GLOBAL general_log = ‘ON’;然后,我们可以查看查询日志文件,例如:ail -f /var/log/mysql/general.log
以上就是使用执行计划诊断MySQL性能问题的基本技巧。在实际工作中,我们可以根据具体情况采用不同的方法来分析执行计划并优化查询性能。五、如何分析 EXPLAIN 结果?在使用EXPLAIN命令获取MySQL查询执行计划后,我们需要对结果进行分析,以便识别潜在的性能问题。以下是一些常用的分析方法:5.1. 扫描类型扫描类型(Scan Type)是显示在查询计划耗时列中的一项指标。通过这个指标,我们可以了解MySQL是否使用了索引或者全表扫描来访问数据。通常情况下,如果出现 ALL、index 和 range 等扫描类型,就意味着性能可能受到影响。其中:ALL 表示全表扫描,即扫描了整个表的数据。index 表示使用了索引扫描,但需要在索引中查找需要的记录。range 表示使用了索引范围查找,即使用了部分索引进行查找。5.2. 关联类型关联类型是指在连接操作中使用的算法。如果查询计划中出现了 Equa Join、Ref、Index Merge 等关联类型,说明MySQL在执行查询时能够充分利用索引,这通常能提高查询性能。其中:Equa Join表示MySQL使用等值连接(内连接),处理两张表中相同键值的行。Ref表示MySQL使用非等值连接,处理两张表中不同键值的行。Index Merge表示MySQL使用了索引合并算法,将多个索引合并来加速查询。5.3. 访问类型访问类型(Access Type) 是指 MySQL 在执行查询时,如何获取数据的方式。常见的访问类型包括:Index: 表示使用了覆盖索引来访问表中的数据,即只需要使用索引就可以获取所需的数据。Index-full scan: 表示使用了全表扫描索引的方式来获取数据,但只访问了索引部分的数据。Full-text: 表示使用了全文搜索来获取数据。5.4. 行数估算查询计划中的 rows 列表示执行某个步骤时,MySQL 估算的行数。如果进行查询的表很大或者存在大量的数据,则行数估算可能会存在偏差。这种情况下,我们需要特别关注具体的查询步骤和访问类型,以确定是否存在性能问题。5.5. 性能优化根据查询执行计划,我们可以判断是否有性能瓶颈,需要对 SQL 查询语句进行优化。优化建议取决于具体的查询计划,例如:在查询计划中使用了索引,请确保使用正确的索引并创建合适的索引来支持查询。如果查询计划使用了全表扫描,请尝试减少查询的数据量以避免全表扫描。如果查询计划中出现了文件排序或临时表操作,请考虑通过更改查询语句或优化表结构来避免这些操作。5.6. 场景分析给大家举一个具体的示例来详细说明如何分析 EXPLAIN 结果。假设我们有以下查询语句:EXPLAIN SELECT *
FROM orders o JOIN customers c ON o.customer_id = c.customer_id
WHERE o.order_date >= '2022-01-01' AND c.address LIKE '%Beijing%';
得到的查询计划结果如下:+----+-------------+-------+------------+------+---------------+------+---------+-----------------------------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+-----------------------------+------+----------+----------------+
| 1 | SIMPLE | c | NULL | ALL | PRIMARY | NULL | NULL | NULL | 1000 | 100.00 | Using where |
| 1 | SIMPLE | o | NULL | ref | customer_id | customer_id | 4 | worldsql.c.customer_id | 2 | 11.11 | Using index |
+----+-------------+-------+------------+------+---------------+------+---------+-----------------------------+------+----------+----------------+
我们将根据这个查询计划结果可以进行如下分析:(1)扫描类型在这个查询计划中,第一行表示 customers 表的扫描类型是 ALL,即执行了全表扫描。这通常会降低MySQL查询性能,因此我们应该检查是否在表上创建了适当的索引来优化查询。(2)关联类型关联类型是 SIMPLE,表示这是一个简单的非子查询。同时,我们还可以看到这里使用了 Equa Join 算法,即使用内连接处理两张表中相同键值的行。这通常是MySQL执行联接操作时的最佳算法之一。(3)访问类型在这个查询计划中,我们还可以看到,orders 表使用了 ref 访问类型,即使用了索引扫描来获取所需数据。这通常比全表扫描更加高效,因此这是一个好的访问类型。(4) 行数估算在这个查询计划中,rows 行数估算列显示为 1000,而实际上 customers 表中只有 1000 行。这意味着MySQL执行了全表扫描并扫描了整个表的所有行。这通常会影响查询性能,因此我们应该检查表上是否存在适当的索引。(5)性能优化针对以上分析结果,我们可以考虑以下优化策略:创建索引:为 customers 表上的 address 列创建索引,以避免全表扫描。设计合适的索引:为 orders 表上的 order_date 和 customer_id 列创建复合索引来支持查询,可以进一步提高查询性能。实际sql查询执行,借助通过分析 EXPLAIN 的结果,我们可以确定如何针对性地优化查询语句以提高性能。六、总结EXPLAIN 命令就像是一个卧虎藏龙的武林秘籍,使用它能够让MySQL查询变得轻松愉快。在这里,我们可以看到每个查询都是一场表演,有着自己的角色和特点。扫描类型就像是一个吃货,它会吃遍整个表才能满足胃口;而关联类型就像是一个心机婊,它总是喜欢暗示你要去找另外的表玩耍。而访问类型则是一个聪明的程序员,它总是想方设法通过索引快速获取所需数据。然而,行数估算却像是一个嘴巴不太靠谱的推销员,它总是高估自己的能力,并且喜欢随意地浪费时间和精力。幸运的是,在这个MySQL世界中,你永远不会孤单。通过使用 EXPLAIN 命令并对查询计划结果进行分析,我们可以更好地理解MySQL执行查询的过程,找到性能瓶颈,并采取相应的优化措施。最后,要记住:优化MySQL查询不仅需要技能,还需要耐心和毅力。但是,当你看到查询计划明显优化后,你会感觉整个人都充满了力量和信心!言归正传,MySQL是一个功能强大的数据库系统,它可以应用于许多不同的场景。为了发挥其最大的性能潜力,我们需要深入了解MySQL的执行计划和查询优化技术,并使用这些知识来诊断和优化查询性能。
无事小神仙
RR有幻读问题吗?MVCC能否解决幻读?
前言幻读是 MySQL 中一个非常普遍,且面试中经常被问到的问题,如果你还搞不懂什么是幻读?什么是 MVCC?以及 MySQL 中的锁?那么请好好收藏和阅读本篇文章,因为它非常重要。RR 隔离级别在 MySQL 中,RR 代表 Repeatable Read(可重复读),是数据库事务隔离级别中的一种,它的特性是保证同一个事务中,多次读取同一条记录时,读取到的数据都是一致的。它也是 MySQL 默认的事务隔离级别。隔离级别是数据库管理系统为了处理并发访问时,控制事务之间相互影响的程度而定义的一组规则。MVCCMVCC(Multi-Version Concurrency Control,多版本并发控制)是一种并发控制机制,用于在数据库系统中处理并发读写操作时保持数据的一致性和隔离性(主要是用来解决幻读问题的)。MVCC 通过在每个数据行上保存多个版本的数据来实现并发读取和写入的一致性。MVCC 的核心思想是将每个事务的读操作与写操作解耦,通过保存数据的历史版本来实现并发控制。每个事务在开始时会创建一个读视图(Read View),用于确定在事务开始时可见的数据版本。读视图包含一个事务开始时的系统版本号,用于与数据行的版本号进行比较,以确定数据行是否对事务可见。在 MVCC 中,当一个事务执行写操作时,会生成一个新的数据版本,并将旧版本的数据保存在回滚日志(Undo Log)中。这样,其他事务在读取数据时仍然可以访问到旧版本的数据,从而避免了幻读问题。MVCC 工作流程如下:读操作:当一个事务执行 SELECT 语句时,会根据读视图的系统版本号和数据行的版本号进行比较,只读取在事务开始之前已经提交的数据行。这样,即使其他事务正在并发地插入或删除数据,事务仍然可以读取到一致的数据。写操作:当一个事务执行 INSERT、UPDATE 或 DELETE 语句时,会生成新的数据版本,并将旧版本的数据保存在回滚日志中。这样,其他事务在读取数据时仍然可以访问到旧版本的数据,从而避免了幻读问题。MVCC 机制在数据库系统中广泛应用,特别是在支持事务的存储引擎中,如 MySQL 的 InnoDB 引擎。它通过解耦读操作和写操作,提供了高并发性能和数据一致性,使得多个事务可以同时读取和修改数据库,而不会相互干扰。RR + MVCC 有幻读问题吗?在 MySQL 中,即使是RR 隔离级别(可重复读),虽然它通过 MVCC 消除了绝大部分幻读问题,但依旧存在部分幻读问题,所以 RR 隔离级别存在幻读问题,而 MVCC 也没有彻底解决幻读问题。幻读问题演示在 RR 隔离级别中存在两种读操作:快照读:数据库中一种读取数据的方式,它基于事务开始时的一个一致性快照来读取数据。快照读可以提供事务开始时的数据视图,即使在事务执行期间其他事务对数据进行了修改,也不会影响快照读取到的数据。简单理解,快照读就是事务开启时创建一个缓存,之后的查询都会从这个缓存中获取数据。当前读:数据库中一种读取数据的方式,它读取最新提交的数据,而不是基于事务开始时的一致性快照。所以,在 RR 隔离级别中 MVCC 通过快照读的方式解决了大部分幻读问题,但如果 RR 隔离级别存在当前读(使用 select … for update 实现),那么此时也会发生幻读问题,比如以下执行过程:如何彻底解决幻读?想要彻底解决幻读问题,有两个方案:使用串行化(Serializable)隔离级别:官方推荐方案,但这种解决方案,并发性能比较低。RR + 锁:使用 RR 隔离级别,但在事务开启之后立即加锁,如下图所示:事务一开启之后就加锁,之后其他事务在操作此表的相关数据时,就只能等待锁释放(事务一提交或回滚锁自动释放)。小结在可重复读级别中,MySQL 虽然使用 MVCC 解决了大部分幻读问题,但在当前读的操作中依然有幻读问题,此时可以通过加锁,或升级隔离级别为串行化来解决幻读问题。
无事小神仙
MySQL 知识点总结
前言MySQL相信大家都耳熟能详了, 毕竟其还不错的性能和免费的特点深受国人的喜爱, 本篇文章将作为我《MySQL》系列的一篇文章, 主要用作整理和简单的概述MySQL相关的一些知识点ok, 接下来我们开始进入正题, 从最简单的开始关系型数据库和非关系型数据库关系型数据库关系型数据库是一个结构化的数据库, 创建在关系模型(二维表格模型)基础上, 一般面向于记录SQL 语句就是一种基于关系型数据库的语言, 用于执行对关系型数据库中数据的检索和操作, 主流的关系型数据库包括: Oracle, MySQL, SQL Server等本篇文章默认以 MySQL的 InnoDB引擎为主非关系型数据库非关系型数据库也叫 NoSQL, 采用键值对的形式进行存储. 一般来讲, 除了主流的关系型数据库之外的数据库, 都认为是非关系型他的读写性能很高, 易于扩展, 主流的 NoSQL有: Redis, MongDB, Hbase等适合使用的场景:日志系统数据量巨大地理位置存储高可用关系型数据库与非关系型数据库之间的区别关系型数据库: 采用了关系模型来组织数据, 容易理解 可以保持数据的一致性 数据更新的开销比较小 支持复杂查询(where子句等)非关系型数据库 不需要经过 SQL层的解析, 读写效率高 基于键值对, 数据的扩展性很好 可以支持多种类型的数据的存储, 图片, 文档等MySQL整体架构SQL 的执行步骤在 MySQL中, Server层按顺序执行SQL的步骤如下:客户端请求连接器(验证用户身份, 给与权限)查询缓存(存在缓存则直接返回, 不存在则执行后续操作)分析器(对SQL进行词法分析和语法分析操作)优化器(主要对执行的 SQL优化选择最优的执行方案方法)执行器(执行时会先看用户是否有执行权限, 有才去使用这个引擎提供的接口)去引擎层获取数据返回(如果开启查询缓存则会缓存查询结果)接下来我将按照 MySQL执行 SQL的步骤来进行讲解MySQL 的架构图示图1 展示了 MySQL各组件之间协同工作的逻辑视图, 也是一条SQL查询的执行流程, 接下来我主要围绕这张图来讲述一下他们和 MySQL之间的关系图1 MySQL架构逻辑视图(图片来自于JavaGuide)客户端的服务主要是包括连接处理, 身份验证, 确保安全性等.大多数 MySQL的核心功能都在第二层, 包括查询解析, 分析, 优化, 以及所有的内置函数, 所有跨存储引擎的功能也都在这一层来实现: 存储过程, 触发器, 视图等第三层是存储引擎层, 负责MySQL中数据的存储和提取. 其架构模式是插件式的, 支持 InnoDB, MyISAM, Memory等多个存储引擎, 现在最常用的存储引擎是 InnoDB, 在 MySQL 5.5版本之后成为了默认存储引擎连接器默认情况下, 每个客户端连接都会在服务器进程中拥有一个线程, 该连接的查询只会在这个线程中执行.当客户端(应用)连接到 MySQL服务器时, 服务器需要对其进行身份验证, 身份验证基于用户名, 密码, 主机ip和端口号, 客户端连接成功之后, 服务器会继续验证该客户端的查询权限(例如, 是否有查看user库的权限, 是否允许对mysql库的user表进行SELECT)创建新连接时身份验证基本信息分析优化和执行查询缓存在执行一条 SELECT查询语句的时候会先去查询缓存看能否直接命中, 能命中就直接返回, 缓存中没有才会去进行下一步只要表有更新操作, 那么这张表的缓存就会更新, 所以对于一张更新较[]频繁的表来说缓存命中是比较低的 从 MySQL 5.7.20版本开始, 查询缓存已经被官方标注为废弃了, 8.0版本完全移除 在 MySQL 8.0版本之前, 想关闭查询缓存可以将参数 query_cache_type 设置为 DEMADN分析器在该步骤主要进行两件事:词法分析根据你的SQL语句识别出关键字来构建语法树, 方便后面的模块获取表名, 字段名, where条件等语法分析根据词法分析结果, 语法分析会判断你输入的SQL语句是否满足 MySQL语法优化器&执行器在优化器中主要做了三件事:prepare 预处理阶段: 查询 SQL中的表, 字段是否存在 将 select * 上的 * 扩列为所有列optimize 优化阶段: 判断当前 SQL语句使用的索引类型, 主键索引, 普通索引, 覆盖索引, 全表扫描等execute 执行阶段: 根据优化结果执行 SQL查询, 从存储引擎中获取查询结果并返回优化器并不关心查询引擎类型, 但是存储引擎对于查询优化是有影响的.存储引擎在 MySQL中, 可以使用 show engines命令来查询 MySQL支持的所有存储引擎可以看到, 当前默认存储引擎是InnoDB, 同时只有InnoDB是支持事务的MyISAM和InnoDB的区别老生常谈了, 也是面试高频考点MyISAMInnoDB行级锁只有表级锁支持行级锁和表级锁, 默认为行级锁事务不支持事务实现了四个隔离级别, 具有提交和回滚事务的能力, 默认使用 REPEATABLE-READ(可重读)隔离级别是可以解决幻读问题发生的外键不支持支持, 在阿里的<java开发手册>中是不推荐使用外键的异常崩溃后的安全恢复不支持支持, redo logMVCC不支持支持事务事务的四大特性原子性: 一个事务中的数据操作, 要么全部成功, 要么全部失败并回滚到执行之前的状态一致性: 事务操作前后, 数据满足完整性约束, 数据库保持一致性状态隔离性: 事务之间相互隔离, 互不影响, 每个事务都有一个完整的数据空间, 在一个事务结束之前对其他事务来讲应该是不存在的持久化: 事务结束之后, 对数据的操作即为永久的, 即便系统故障也不会消失隔离级别未提交读 (READ UNCOMMITTED): 在事务中可以查看其他事务未提交的修改 读取未提交的事务也叫脏读读提交 (READ COMMITTED):一个事务可以看到其他事务在他开始之后提交的修改 同一个事务两次执行相同语句可能会看到不同的数据结果, 不可重复读M可重复读(REPEATABLE READ): 同一个事务在多次读取相同行数据的结果相同 当一个事务执行范围查询过程中, 另外一个事务对该范围进行了插入操作, 当再次对该范围进行查询的时候, 就会出现幻行, 也就是幻读串行化(SERIALIZABLE):最高的事务隔离级别, 解决了幻读问题. 其会在读取的每一行数据都进行加锁操作多个事务之间引发的隔离问题脏读: 读取未提交的事务不可重复读: 同一个事务两次执行相同语句可能会看到不同的数据结果幻读: 当一个事务执行范围查询过程中, 另外一个事务对该范围进行了插入操作, 当再次对该范围进行查询的时候, 就会出现幻行。
无事小神仙
InnoDB主键索引树和二级索引树
我们这里讨论InnoDB存储引擎,数据和索引存储在同一个文件student.ibd场景1:主键索引树uid是主键,其他字段没有添加任何索引select * from student;
如果是上面这样查询,这表示整表搜索,从左到右遍历叶子节点链表,从小到大访问select * from student where uid<5;
如果是上面这样查询,这表示范围查询,就直接在有序链表中遍历搜索就可以了,直到遍历到第一个不小于5的key结束遍历select * from student where uid=5;
如果是上面这样查询,这表示等值查询,在索引树上进行二分查找即可由于name没有索引,于是做整表搜索select * from student where name='linfeng';
场景2:二级索引树uid是主键,以name创建了普通索引(二级索引)以name为索引构建的索引树,称为辅助索引树,也叫做二级索引树。key是辅助索引字段name的值,然后还有外加uid主键的值在辅助索引树上,key是辅助索引的值,也就是name;data数据值是所在记录行的主键值(PRIMARY KEY),也就是uid(并不是表的一行数据),一行表数据只在主键索引树上存在分析语句1:select name from student where name='linfeng';
因为过滤字段是name且 只select了name一个字段,name有索引,索引树上直接就有,所以从name的二级索引树上去等值匹配linfeng分析语句2:select uid,name from student where name='linfeng';
这种情况select的是name和uid,而这些在二级索引树上也是直接就有,所以搜索二级索引树就完事了。分析语句3:select * from student where name='linfeng';
这种情况下就涉及到回表了,这是一个很重要的概念。由于name字段有索引,所以我们会到name字段构建的二级索引树上去查找。但二级索引树没有linfeng这个人所有的信息,所以完整的查询过程应该是这样的:用linfeng到二级索引树上进行匹配,拿到二级索引树上存储的uid然后拿着这个uid去主索引树上去匹配,最后拿到linfeng的所有信息(回表)而这个回表意味着更多的磁盘I/O,会影响效率,如果业务只需要uid、name,就别写select *了,这样可以避免回表(在二级索引树上查到主键,再去主键索引树上查找)分析语句4:我们删除name的索引后执行以下语句select * from student where age=20 order by name;
没有用到索引,还使用外部排序了。此外我们还看到using filesort,这时需要优化了。我们的过滤条件是age,先给age添加索引,看看行不行可以看到,age命中索引了,查询age所在的索引树。由于我们写的是select *,依然存在回表。还有using filesort,因为使用age=20查询到的结果是多个,然而name此时是没有顺序的,所以还需要再进行外部排序。那能不能通过给name加载索引来解决问题呢?不能,因为一次SQL执行只能用到1个索引,搜索了这个字段的索引树就不会再去搜索另一个字段的索引树了,因为加载索引是要耗费磁盘I/O的,查找多个索引树就太慢了!分析:既然索引树上只能存自己建立的索引字段以及主键,那我们把需要查询的字段都设置成索引不就好了?解决方法:我们可以在二级索引树上的key:age+name,形成联合索引,先按age排序,age相同了,再按name排序再次select *这时候就使用到联合索引了,而且没有using filesort,这次是这样查询的:先用age=20在辅助索引树上查找,如果数据足够会找到多个结果,这个结果就是已经排好序的,不需要再using filesort我们现在直接用第二个字段name作为过滤条件我们看到这里没有用到索引,因为我们用(age,name)创建索引,是先按age排序,再按name排序。如果我们只用name作为过滤条件,这就没有办法使用索引匹配了,因为是优先用age排序。所以我们经常说:多列索引一定要使用到第1个字段,这样才能用到索引!在建立(age,name)联合索引的情况下,以下操作不回表(到二级索引树上搜索,再去主索引树上搜索):select ageselect age, nameselect uid,age,name以下操作要回表select *select age,name,sex
无事小神仙
PHP处理库存超卖的几种处理方法
第一种方法:使用mysql数据库的锁机制。在事务中使用 for update 语句,在事务处理完成之后释放这一条数据。代码使用tp5的框架:public function mysqlLock(){
$goods_id = 26545;
$sku_id = 26545;
$price = 300;
$user = '';
StoreOrderModel::startTrans();
$nums = StoreOrderModel::where(['id'=>1])->field('number')->lock(true)->find();
$nums = $nums['number'];
if($nums > 0){
$item['goods_id'] = $goods_id;
$item['sku_id'] = $sku_id;
$item['number'] = $nums;
$item['price'] = $price;
$item['user'] = $user;
$id = StoreModel::insertGetId($item);
if($id){
StoreOrderModel::where(['id'=>1])->setDec('number');
StoreOrderModel::commit();
}else{
StoreOrderModel::rollback();
}
}else{
echo "没有库存了";
}
}
第二种方法:redis 事务。public function start_reids_tran(){
$goods_id = 26545;
$sku_id = 26545;
//$number = 1;
$price = 300;
$user = '';
$redis = ResRedisModel::getinstance();
$redis->watch('store');
$nums = intval($redis->get('store'));
if($nums > 0){
$item['goods_id'] = $goods_id;
$item['sku_id'] = $sku_id;
$item['number'] = $nums;
$item['price'] = $price;
$item['user'] = $user;
$redis->lPush('success', json_encode($item));
$redis->multi();
$redis->decr('store');
$replies = $redis->exec(); // 执行以上 redis 事务
if(!$replies){
echo "订单 {$nums} 回滚".PHP_EOL;
}
$redis->unwatch();
echo "抢购成功!".PHP_EOL;
}else{
echo "没有库存了";
}
}
第三种方法:redis 队列,预先把库存信息存入队列当中,抢购时判断队列的数量,然后出队。队列为空时库存为0。public function eq_start(){
$redis = ResRedisModel::getinstance();
$nums = $redis->lSize('store');
$goods_id = 26545;
$sku_id = 26545;
$number = 1;
$price = 300;
$user = '';
if($nums > 0){
$user = $redis->rPop('store');
if($user){
$item['goods_id'] = $goods_id;
$item['sku_id'] = $sku_id;
$item['number'] = $number;
$item['price'] = $price;
$item['user'] = $user;
StoreModel::insertGetId($item);
echo '抢购成功!';
}else{
echo '抢购失败!';
}
}else{
echo '抢购失败!';
}
}
第四种:文件排他锁方式public function file_star(){
$fp = fopen('D:/phpStudy/PHPTutorial/www/public/lock.txt', "r");
if(flock($fp, LOCK_EX)) { //排他型锁定 阻塞模式 , flock($fp,LOCK_EX | LOCK_NB) 非阻塞模式
$nums = StoreOrderModel::where(['id'=>1])->field('number')->find();
$nums = $nums['number'];
if($nums > 0){
$goods_id = 26545;
$sku_id = 26545;
$number = 1;
$price = 300;
$user = '213';
$item['goods_id'] = $goods_id;
$item['sku_id'] = $sku_id;
$item['number'] = $number;
$item['price'] = $price;
$item['user'] = $user;
StoreModel::insertGetId($item);
StoreOrderModel::where(['id'=>1])->setDec('number');
flock($fp, LOCK_UN); //释放锁定
echo '抢购成功!';
}else{
echo '没有库存了!';
}
}else{
echo '抢购失败!';
}
fclose($fp);
}
无事小神仙
MySQL 总结char与varchar的区别
MySQL手册中有提到:CHAR和VARCHAR类型类似,但它们保存和检索的方式不同。它们的最大长度以及是否保留尾部空格等方面也不同,在存储或检索过程中不进行大小写转换用户定义数据时,char和varchar类型长度表示想保存的最大字符数,其中char(M)定义的列的长度为固定的,M的取值可以0-255之间,保存char类型数据时,在它们的右边填充空格以达到指定的长度。当检索到char值时,尾部的空格被删除掉。在存储或检索过程中不进行大小写转换,char存储定长数据很方便,char字段上的索引效率很高。varchar(M)定义的列的长度是可变长度字符串,在MySQL5.0以上的版本中,varchar的数据类型长度支持到了65535,因为起始位和结束位占去了3个字节,所以其用于存储数据的最大长度为65532字节(varchar的最大有效长度由最大行大小和使用的字符集确定)除此之外,varchar值保存时只保存数据部分,不用和char一样填充空格,此外另加一个字节来记录长度(长度超过255时需要2个字节),检索时char类型后的空格被删掉,而不会删除varchar类型后的空格在MySQL数据库中,用的最多的字符型数据类型就是VARCHAR和CHAR。这两种数据类型虽然都是用来存放字符型数据,但是无论从结构还是从数据的保存方式来看,两者相差很大。而且其具体的实现方式,还依赖于存储引擎。这里就以MYISAM存储引擎为例,谈谈这两种数据类型的差异这里首先需要明白的一点是,这两种数据类型,无论采用哪一种存储引擎,系统存储VARCHAR和CHAR的方式都是不同的。正是因为如此,我们才有必要研究两者的不同。然后在合适的情况下,采用恰当的方式。Varchar往往用来保存可变长度的字符串。简单的说,我们只是给其固定了一个最大值,然后系统会根据实际存储的数据量来分配合适的存储空间。因此相比CHAR字符数据而言,其能够比固定长度类型占用更少的存储空间。不过在实际工作中,由于某些特殊的原因,会在这里设置例外。如管理员可以根据需要指定ROW_FORMAT=FIXED选项。利用这个选项来创建MyISAM表的话,系统将会为每一行使用固定长度的空间,此时会造成存储空间的损耗。通常情况下,VARCHAR数据类型能够节约磁盘空间,因此往往认为其能够提升数据库的性能。不过这里需要注意的是,这往往是一把双刃剑。其在提升性能的同时,往往也会产生一些副作用。如因为其长度是可变的,数据更新时可能会导致一些额外的工作。比如在更改前,其字符长度是10位(假设用户指定VARCHAR的最大长度为50),此时系统就只给其分配10个存储的位置(假设不考虑系统自身的开销)。更改后,其数据量达到了20位。由于没有超过最大50位的限制,为此数据库还是允许其存储的。由于原先的存储位置已经无法满足其存储的需求,需要存储到另一片大内存中,此时系统就需要进行额外的操作。如根据存储引擎不同,有的会采用拆分机制,而有的则会采用分页机制CHAR数据类型与VARCHAR数据类型不同,其采用的是固定长度的存储方式。简单的说,就是系统总为其分配最大的存储空间。当数据保存时,即使其没有达到最大的长度,系统也会为其分配这么多的存储空间。显然,这种存储方式会造成磁盘空间的浪费显然,VARCHAR与CHAR两种字符型数据类型,最大的差异就是VARCHAR是可变长度,而CHAR则是固定长度。在存储时,VARCHAR会根据实际存储的数据来分配最终的存储空间。而CHAR则不管实际存储数据的长度,都是根据规定的长度来分配存储空间。这是否意味着CHAR的数据类型劣于VARCHAR呢?其实不然。否则的话,就没有必要存在CHAR字符类型了。虽然VARCHAR数据类型可以节省存储空间,提高数据处理的效率。但是其可变长度带来的一些负面效应,有时候会抵消其带来的优势。为此在某些情况下,还是需要使用CHAR数据类型注意:使用VARCHAR数据类型,也不能够太过于慷慨。这是什么意思呢?如现在用户需要存储一个地址信息。根据评估,只要使用100个字符就可以了。但是有些数据库管理员会认为,反正Varchar数据类型是根据实际的需要来分配长度的,还不如给其大一点的呢。为此可能会为这个字段一次性分配200个字符的存储空间。这VARCHAR(100)与VARCHAR(200)真的相同吗?结果是否定的。虽然他们用来存储90个字符的数据,其存储空间相同,但消耗的内存是不同的。对于VARCHAR数据类型来说,硬盘上的存储空间虽然都是根据实际字符长度来分配存储空间的,但是对于内存来说,则不是。其实使用固定大小的内存块来保存值。简单的说,就是使用字符类型中定义的长度,即200个字符空间。显然,这对于排序或者临时表(这些内容都需要通过内存来实现)作业会产生比较大的不利影响。所以如果某些字段会涉及到文件排序或者基于磁盘的临时表时,分配VARCHAR数据类型时仍然不能够太过于慷慨。还是要评估实际需要的长度,然后选择一个最长的字段来设置字符长度。如果为了考虑冗余,可以留10%左右的字符长度。千万不能认为其为根据实际长度来分配存储空间,而随意的分配长度,或者说干脆使用最大的字符长度。适用情况:对于MyISAM表,尽量使用CHAR,对于那些经常需要修改而容易形成碎片的数据表就更是如此,它的缺点就是占用磁盘空间;对于InnoDB表,因为它的数据行内部存储格式对固定长度的数据行和可变长度的数据行不加区分(所有数据行共用一个表头部分,这个标头部分存放着指向各有关数据列的指针),所以使用char类型不见得会比使用varchar类型好。事实上,因为char类型通常要比varchar类型占用更多的空间,所以从减少空间占用量和减少磁盘i/o的角度,使用varchar类型反而更有利;存储很短的信息,比如门牌号码101,201……这样很短的信息应该用char,因为varchar还要使用1个字节用于存储信息长度,本来打算节约存储的现在得不偿失固定长度的。比如使用uuid作为主键,那用char应该更合适。因为他固定长度,varchar动态根据长度的特性就消失了,而且还要占1个字节长度信息。十分频繁改变的column。因为varchar每次存储都要有额外的计算,得到长度等工作,如果一个非常频繁改变的,那就要有很多的精力用于计算,而这些对于char来说是不需要的。
无事小神仙
MySQL哈希索引以及InnoDB自适应哈希索引
一、哈希索引哈希索引是基于内存的支持,底层结构就是链式哈希表,增删改查的时间复杂度都是O(1),一断电就没了,因为内存搜索,哈希表是最快的而平衡树的增删改查的时间复杂度是O(long2n),此外B+树索引是把磁盘上的存储的索引加载到内存上构建的数据结构。看起来哈希表比B+树好,那为什么MyISAM和InnoDB存储引擎用的是B+树索引?我们主要看搜索的效率磁盘I/O的花费我们改用创建哈希索引来看看:查看索引的底层实现,应使用如下语句:show indexes from student;
假设我们给name字段创建哈希索引构建链式哈希表:根据选定的哈希函数,把每一行记录的name字段作为参数来求一个哈希值,哈希值对桶的长度取模得到桶的序号(会产生哈希冲突),然后进行存储。索引值和数据存储在一起,类似于InnoDB解决哈希冲突的方式:在桶里面用链表串起来(链地址法)注意:虽然链式哈希表的桶看起来有顺序,实际上存储的索引值是没有任何顺序的,不仅是桶之间没有顺序,桶内元素也没有任何顺序。因为我们用哈希函数进行了计算,然后还进行了取模的操作,不可能说我输入的索引值的字典序小,就一定在需要小的桶里面链式哈希表快仅仅只是在等值查找的时候快,比如:select * from student name="zhangsan";
一旦我们进行范围查找、模糊查找等一系列操作时,链式哈希表就无能为力了,比如:select * from student name like "zhang%";
此时搜索引擎完全不知道前缀是zhang的数据在哪,也不知道给哈希函数输入什么,这个时候只能做整表搜索,也就是O(n)此外假设age也有哈希索引,如下的查找方式,哈希表就需要计算18~30所有的哈希值,然后查找数据select * from student where age between 18 and 30;
在哈希表中,不同元素,哪怕是15和16,通过求哈希值,模上桶的个数,最后存储的位置可能会相隔很远。如果用链式哈希表构建索引,一个桶里面的节点代表1次磁盘I/O,由于桶内元素也是没有顺序的,我们进行查找的时候都会遍历完所有的桶内节点,就会导致更多的磁盘I/O。哈希索引只适用于小数据量的,在内存上的等值查询,处理不在磁盘的数据,并不能为我们减少磁盘I/O的次数!!!总结:由于我们绝大部分的数据都是存放在磁盘的,哈希索引没办法减少磁盘I/O的次数,从磁盘上加载数据到内存的次数太多由于不同的索引值经过哈希函数计算以及取模后,最后存储的位置非常不确定,没有任何的顺序,故不适用于多数的应用场景,比如范围、模糊、排序等等此外一旦哈希表扩容,就会导致所有的索引值重新计算存储位置,效率很低二、InnoDB自适应哈希索引自适应哈希索引作用:MySQL Server为避免频繁回表,会使用频繁访问的二级索引项创建哈希索引假如name是有索引的,我们不断使用如下的方式查询,那就得先访问name的二级索引树,从二级索引树上取出主键uid,然后回表,用这个uid去主键索引树上取得对应的数据select * from student where name = "zhangsan";
select * from student where name = "gaoyang";
select * from student where name = "linfeng";
...
The hash index is always built based on an existing B-tree index on the table. InnoDB can build a hash index on a prefix of any length if the key defined for the B-treeInnoDB存储引擎会做如下优化:如果检测到某个二级索引不断被使用,二级索引成为热数据,那么InnoDB会根据在二级索引树上的索引值在构建一个哈希索引来加速搜索(只适用于等值比较)图中蓝色的箭头表示不建立哈希索引,搜索二级索引树然后回表的过程黄色箭头就是直接等值比较搜索哈希表,直接拿到数据地址的过程。使用哈希索引O(1)的时间复杂度就访问到哈希索引name,然后取出data即可(对于InnoDB来说应该是直接取得数据,而不是拿到数据地址后再访问)注意:hash索引的生成和维护也是耗费性能的,并不能绝对的在任何场景下提高对二级索引的搜索效率,我们可以查看相关参数指标,如果自适应哈希索引可以提高效率,那我们使用它,否则我们就关闭它自适应哈希索引是默认开启的:在MySQL5.7以前,操作哈希表是只有一把锁的,锁的粒度太大,效率很低。在MySQL5.7以后,每个分区都会有自己的锁,锁的粒度减小,要是各个线程在同一个分区(一个分区可以包含一个或多个桶)进行并发操作,就需要加锁。要是在不同的分区操作,就不用加锁。You can monitor the use of the adaptive hash index and the contention for its use in the SEMAPHORES section of the output of the SHOW ENGINE INNODB STATUS command. If you see many threads waiting on an RW-latch created in btrOsea.c, then it might be useful to disable adaptive hash indexing.在并发环境中,如果同一个分区等待的线程过多,这个时候需要考虑关闭自适应哈希索引我们通过以下命令查看两个关键信息:show engine innodb status\G
RW-latch等待线程的数量,自适应哈希索引默认分配了8个分区,若某个分区等待的线程数量过多,则需要考虑关闭自适应哈希索引使用AHI搜索的频率低于不使用AHI搜索的频率,也需要考虑关闭自适应哈希索引项目中如果遇到并发量很大,服务器处理请求慢时,可以使用show engine innodb status\G查看是否需要关闭AHI,也是提高数据库性能的一种方式。
无事小神仙
MySQL百万数据深度分页优化思路分析
一、业务背景一般在项目开发中会有很多的统计数据需要进行上报分析,一般在分析过后会在后台展示出来给运营和产品进行分页查看,最常见的一种就是根据日期进行筛选。这种统计数据随着时间的推移数据量会慢慢的变大,达到百万、千万条数据只是时间问题。二、瓶颈再现创建了一张user表,给create_time字段添加了索引。并在该表中添加了100w条数据。我们这里使用limit分页的方式查询下前5条数据和后5条数据在查询时间上有什么区别。查询前10条基本上不消耗什么时间我们从第50w+开始取数据的时候,查询耗时1秒。SQL_NO_CACHE 这个关键词是为了不让SQL查询走缓存。同样的SQL语句,不同的分页条件,两者的性能差距如此之大,那么随着数据量的增长,往后页的查询所耗时间按理会越来越大。三、问题分析回表我们一般对于查询频率比较高的字段会建立索引。索引会提高我们的查询效率。我们上面的语句使用了SELECT * FROM user,但是我们并不是所有的字段都建立了索引。当从索引文件中查询到符合条件的数据后,还需要从数据文件中查询到没有建立索引的字段。那么这个过程称之为回表。覆盖索引如果查询的字段正好创建了索引了,比如 SELECT create_time FROM user,我们查询的字段是我们创建的索引,那么这个时候就不需要再去数据文件里面查询,也就不需要回表。这种情况我们称之为覆盖索引。IO回表操作通常是IO操作,因为需要根据索引查找到数据行后,再根据数据行的主键或唯一索引去聚簇索引中查找具体的数据行。聚簇索引一般是存储在磁盘上的数据文件,因此在执行回表操作时需要从磁盘读取数据,而磁盘IO是相对较慢的操作。LIMTI 2000,10 ?你有木有想过LIMIT 2000,10会不会扫描1-2000行,你之前有没有跟我一样,觉得数据是直接从2000行开始取的,前面的根本没扫描或者不回表。其实这样的写法,一个完整的流程是查询数据,如果不能覆盖索引,那么也是要回表查询数据的。现在你知道为什么越到后面查询越慢了吧!四、问题总结我们现在知道了LIMIT 遇到后面查询的性能越差,性能差的原因是因为要回表,既然已经找到了问题那么我们只需要减少回表的次数就可以提升查询性能了。五、解决方案既然覆盖索引可以防止数据回表,那么我们可以先查出来主键id(主键索引),然后将查出来的数据作为临时表然后 JOIN 原表就可以了,这样只需要对查询出来的5条结果进行数据回表,大幅减少了IO操作。优化前后性能对比我们看下执行效果:优化前:1.4s优化后:0.2s查询耗时性能大幅提升。这样如果分页数据很大的话,也不会像普通的limit查询那样慢。
无事小神仙
MySQL事务的ACID特性以及并发问题
一、事务概念InnoDB支持事务,而MyISAM不支持事务一个事务是由一条或者多条对数据库操作的SQL语句所组成的一个不可分割的单元,只有当事务中的所有操作都正常执行完了,整个事 务才会被提交给数据库;如果有部分事务处理失败,那么事务就要回退到最初的状态,因此,事务要么全部执行成功,要么全部失败。所以记住事务的几个基本概念,如下:事务是一组SQL语句的执行,要么全部成功,要么全部失败,不能出现部分成功,部分失败的结果。保证事务执行的原子性事务的所有SQL语句全部执行成功,才能提交(commit) 事务,把结果写回磁盘事务执行过程中,有SQL出现错误,那么事务必须要回滚(rollback) 到最初的状态比如转账业务需要多条SQL语句共同完成,只有这些SQL都执行成功才算业务成功了begin开启事务,如果这2句SQL都成功了,那么commit提交一个事务如果其中任意一条SQL由于停电,或者服务器出错,导致SQL执行异常,那事务就没有提交,事务会回滚,数据将恢复到事务开始前的状态这是存储引擎来保证的(redo log和undo log保证的)查看当前数据库支持的存储引擎show engines;
数据库引擎可以通过命令临时修改,或者通过配置文件永久修改select @@autocommit;
@autocommit为1表示事务自动提交,为0表示事务手动提交做业务的时候,我们一般会在代码上控制这个变量,一般来说,我们的事务由多条SQL组成,所以我们设置为手动提交。业务都成功,则提交这个事务;如果业务中间出现失败,就回滚1个事务二、ACID特性每一个事务必须满足下面的4个特性:原子性(Atomic):事务是一个不可分割的整体,事务必须具有原子特性。当修改数据时,要么全执行,要么全不执行,即不允许部分事务完成一致性(Consistency):事务执行之前和执行之后,数据库数据必须保持一致性状态。数据库的一致性状态必须由用户来负责,由并发控制机制实现。拿银行转账来说,只有让一个用户的余额减少,又让一个用户的余额增加才能构成一个完整的事务隔离性(Isolation):当两个或者多个事务并发执行时,为了保证数据的安全性,将一个事物内部的操作与其它事务的操作隔离起来,不被其它正在执行的事务所看到,使得并发执行的各个事务之间不能互相影响。隔离级别:数据的安全性和事务的并发性。隔离越严格,安全性越高,并发性越低(就是并发控制,保证数据安全)持久性(Durability):事务完成(事务commit成功)以后,DBMS保证它对数据库中的数据的修改是永久性的,即使数据库因为故障出错,也应该能够恢复数据InnoDB 引擎通过什么技术来保证事务的这四个特性的呢?持久性是通过 redo log (重做日志)来保证的原子性是通过 undo log(回滚日志) 来保证的隔离性是通过 MVCC(多版本并发控制) 或锁机制来保证的一致性则是通过持久性+原子性+隔离性来保证DB写数据都是先在cache缓存上写的(因为速度快),然后操作系统通过磁盘I/O往磁盘上写,当事务成功提交后,commit就返回了。然后cache再慢慢往磁盘上写数据,这个过程中如果由于不可抗因素中断了,导致缓存上的数据向磁盘上写的时候没写完,那此时数据就丢了。系统重启后MySQL数据库会根据redo log来重新执行这个事务并写入缓存,然后写入磁盘,来保证数据库的持久性。由于用户会写很多数据,所以commit不会等着这些数据从缓存全部写到磁盘再返回,因为要经过磁盘I/O,业务上不可能让用户去等那么长时间MySQL最重要的是日志,不是数据!事务的ACD特性由redo log和undo log机制保证,事务的I特性由事务的锁机制来保证,锁粒度越大,事务隔离性越好,安全性越高,并发性越低,效率越低三、事务并发存在的问题事务处理如果不经隔离,并发执行事务时通常会发生以下问题:脏读(Dirty Read):一个事务读取了另一个事务未commit的数据(处理了一半的数据) 。例如当事务A和事务B并发执行时,当事务A更新后,事务B查询读取到A尚未提交的数据,此时如果事务A rollback了,那事务B读到的数据就不是数据库所存放的数- 据了,而是无效的脏数据(脏读必须杜绝,因为事务没有commit;而不可重复读和幻读不一定出问题,因为事务已经commit)不可重复读(NonRepeatable Read):一个事务的操作导致另一个事务前后两次读取到不同的数据 。例如当事务A和事务B并发执行时,当事务B查询读取数据后,事务A update操作更改事务B查询到的数据,此时事务B再次去读该数据,发现前后两次读的数据不一样(事务B读取了事务A已commit的数据)幻读(Phantom Read):一个事务的操作导致另一个事务前后两次查询的结果数据量不同。例如 当事务A和事务B并发执行时,当事务B查询读取数据后,事务A新增或者删除了一条满足事务B查询条件的记录,此时事务B再去查询,发现查询到前一次不存在的记录,或者前一次查询的一些记录不见了(事务B读取了事务A新增加的数据或者读不到事务A删除的数据)在有些场景下,不可重复读和幻读一定程度上是可以允许的,不一定非要杜绝(通过设置不同的隔离级别解决),由应用场景需求决定脏读举例:张三的账户还剩100块,他同时发起两个事务,事务A转账50,事务B购买价格为80的水杯,事务A现对张三的余额减50,然后给另一个人余额加50。由于没有隔离控制,事务B购买水杯前先读取余额,发现只有50,无法购买茶杯,于是茶杯购买失败。此时事务A执行异常,将张三的余额回滚为事务执行前的状态,余额为100。当前场景中事务B读到了事务A还没有commit的数据50,发生了脏读,任何业务场景下都必须杜绝不可重复读举例:首先事务B查询余额,发现为100。事务A完成转账50,并且commit,事务B再次查询余额,发现变成了50,在某些业务场景下是可以允许的,不一定非要杜绝幻读举例:事务B查询年龄为20的人,发现有5个。事务A插入或删除了年龄为20的记录,并且commit,事务B再次查询年龄为20的人,发现已经不是5个人了,幻读也是在某些业务场景下是可以允许的,不一定非要杜绝四、事务相关命令查看MySQL是否自动提交事务1表示自动提交事务,0表示手动提交事务一般我们业务上如果要考虑到事务处理,我们需要设置为手动提交方式,如果一个事务包含多个SQL,若是自动提交方式,一句SQL执行完就自动提交了,后面的SQL万一执行失败就无法正常rollback,无法保证事务的原子特性BEGIN:开启一个事务COMMIT:提交一个事务ROLLBACK:回滚一个事务到初始的位置SAVEPOINT point1:设置一个名字为point1的保存点ROLLBACK TO point1:事务只回滚到保存点point1,而不是回滚到初始状态SET TRANSACTION_ISOLATION=‘REPEATABLE-READ’:设置事务的隔离级别SELECT @@TRANSACTION_ISOLATION:查询事务的隔离级别
无事小神仙
MySQL和B树的不知道的那些事
一、零铺垫在介绍B树之前,先来看另一棵神奇的树——二叉排序树(Binary Sort Tree),首先它是一棵树,“二叉”这个描述已经很明显了,就是树上的一根树枝开两个叉,于是递归下来就是二叉树了(下图所示),而这棵树上的节点是已经排好序的,具体的排序规则如下:若左子树不空,则左子树上所有节点的值均小于它的根节点的值若右子树不空,则右子树上所有节点的值均大于它的根节点的值它的左、右子树也分别为二叉排序数(递归定义)从图中可以看出,二叉排序树组织数据时,用于查找是比较方便的,因为每次经过一次节点时,最多可以减少一半的可能,不过极端情况会出现所有节点都位于同一侧,直观上看就是一条直线,那么这种查询的效率就比较低了,因此需要对二叉树左右子树的高度进行平衡化处理,于是就有了平衡二叉树(Balenced Binary Tree)。所谓“平衡”,说的是这棵树的各个分支的高度是均匀的,它的左子树和右子树的高度之差绝对值小于1,这样就不会出现一条支路特别长的情况。于是,在这样的平衡树中进行查找时,总共比较节点的次数不超过树的高度,这就确保了查询的效率(时间复杂度为O(logn))。二、B树的起源B树,最早是由德国计算机科学家Rudolf Bayer等人于1972年在论文 《Organization and Maintenance of Large Ordered Indexes》提出的,不过我去看了看原文,发现作者也没有解释为什么就叫B-trees了,所以把B树的B,简单地解释为Balanced或者Binary都不是特别严谨,也许作者就是取其名字Bayer的首字母命名的也说不定啊……三、B树长啥样还是直接看图比较清楚,图中所示,B树事实上是一种平衡的多叉查找树,也就是说最多可以开m个叉(m>=2),我们称之为m阶b树,为了体现本博客的良心之处,不同于其他地方都能看到2阶B树,这里特意画了一棵5阶B树 。总的来说,m阶B树满足以下条件:每个节点至多可以拥有m棵子树根节点,只有至少有2个节点(要么极端情况,就是一棵树就一个根节点,单细胞生物,即是根,也是叶,也是树)。非根非叶的节点至少有的Ceil(m/2)个子树(Ceil表示向上取整,图中5阶B树,每个节点至少有3个子树,也就是至少有3个叉)。非叶节点中的信息包括[n,A0,K1,A1,K2,A2,…,Kn,An],,其中n表示该节点中保存的关键字个数,K为关键字且Ki<Ki+1,A为指向子树根节点的指针。从根到叶子的每一条路径都有相同的长度,也就是说,叶子节在相同的层,并且这些节点不带信息,实际上这些节点就表示找不到指定的值,也就是指向这些节点的指针为空。B树的查询过程和二叉排序树比较类似,从根节点依次比较每个结点,因为每个节点中的关键字和左右子树都是有序的,所以只要比较节点中的关键字,或者沿着指针就能很快地找到指定的关键字,如果查找失败,则会返回叶子节点,即空指针。例如查询图中字母表中的K从根节点P开始,K的位置在P之前,进入左侧指针左子树中,依次比较C、F、J、M,发现K在J和M之间沿着J和M之间的指针,继续访问子树,并依次进行比较,发现第一个关键字K即为指定查找的值四、Plus版——B+树作为B树的加强版,B+树与B树的差异在于有n棵子树的节点含有n个关键字(也有认为是n-1个关键字)所有的叶子节点包含了全部的关键字,及指向含这些关键字记录的指针,且叶子节点本身根据关键字自小而大顺序连接非叶子节点可以看成索引部分,节点中仅含有其子树(根节点)中的最大(或最小)关键字B+树的查找过程,与B树类似,只不过查找时,如果在非叶子节点上的关键字等于给定值,并不终止,而是继续沿着指针直到叶子节点位置。因此在B+树,不管查找成功与否,每次查找都是走了一条从根到叶子节点的路径。五、MySQL是如何使用B树的说明:事实上,在MySQL数据库中,诸多存储引擎使用的是B+树,即便其名字看上去是BTREE。1、innodb的索引机制先以innodb存储引擎为例,说明innodb引擎是如何利用B+树建立索引的。首先创建一张表:zodiac,并插入一些数据CREATE TABLE `zodiac` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` char(4) NOT NULL,
PRIMARY KEY (`id`),
KEY `index_name` (`name`)
);
insert zodiac(id,name) values(1,'鼠');
insert zodiac(id,name) values(2,'牛');
insert zodiac(id,name) values(3,'虎');
insert zodiac(id,name) values(4,'兔');
insert zodiac(id,name) values(5,'龙');
insert zodiac(id,name) values(6,'蛇');
insert zodiac(id,name) values(7,'马');
insert zodiac(id,name) values(8,'羊');
insert zodiac(id,name) values(9,'猴');
insert zodiac(id,name) values(10,'鸡');
insert zodiac(id,name) values(11,'狗');
insert zodiac(id,name) values(12,'猪');
对于innodb来说,只有一个数据文件,这个数据文件本身就是用B+树形式组织,B+树每个节点的关键字就是表的主键,因此innodb的数据文件本身就是主索引文件,如下图所示,主索引中的叶子页(leaf page)包含了数据记录,但非叶子节点只包含了主键,术语“聚簇”表示数据行和相邻的键值紧凑地存储在一起,因此这种索引被称为聚簇索引,或聚集索引。这种索引方式,可以提高数据访问的速度,因为索引和数据是保存在同一棵B树之中,从聚簇索引中获取数据通常比在非聚簇索引中要来得快。所以可以说,innodb的数据文件是依靠主键组织起来的,这也就是为什么innodb引擎下创建的表,必须指定主键的原因,如果没有显式指定主键,innodb引擎仍然会对该表隐式地定义一个主键作为聚簇索引。同样innodb的辅助索引,如下图所示,假设这些字符是按照生肖的顺序排列的(其实我也不知道具体怎么实现,不要在意这些细节,就是举个例子),其叶子节点中也包含了记录的主键,因此innodb引擎在查询辅助索引的时候会查询两次,首先通过辅助索引得到主键值,然后再查询主索引,略微有点啰嗦。。。2、MyISAM的索引机制MyISAM引擎同样也使用B+树组织索引,如下图所示,假设我们的数据不是按照之前的顺序插入的,而是按照图中的是顺序插入表,可以看到MyISAM引擎下,B+树叶子节点中包含的是数据记录的地址(可以简单理解为“行号”),而MyISAM的辅助索引在结构上和主索引没有本质的区别,同样其叶子节点也包含了数据记录的地址,稍微不同的是辅助索引的关键字是允许重复。六、简单对比1、Innodb辅助索引的叶子节点存储的不是地址,而是主键值,这样的策略减少了当出现行移动或者数据页分裂时辅助索引的维护工作,虽然使用主键值当作指针会让辅助索引占用更多空间,但好处是,Innodb在移动行时无需更新辅助索引中的主键值,而MyISAM需要调整其叶子节点中的地址。2、innodb引擎下,数据记录是保存在B+树的叶子节点(大小相当于磁盘上的页)上,当插入新的数据时,如果主键的值是有序的,它会把每一条记录都存储在上一条记录的后面,但是如果主键使用的是无序的数值,例如UUID,这样在插入数据时Innodb无法简单地把新的数据插入到最后,而是需要为这条数据寻找合适的位置,这就额外增加了工作,这就是innodb引擎写入性能要略差于MyISAM的原因之一。Innodb和MyISAM索引的抽象图
无事小神仙
MySQl中的乐观锁是怎么实现的
前言mysql中的乐观锁是怎么实现的?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。一、乐观锁乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。优点:从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销(操作员 A和操作员 B 操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系统整体性能表现。缺点:需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户余额更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。二、如何实现乐观锁呢,一般来说有以下2种方式2.1、使用数据版本(Version)记录机制实现这是乐观锁最常用的一种实现 方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录 的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数 据。用下面的一张图来说明:如上图所示,如果更新操作顺序执行,则数据的版本(version)依次递增,不会产生冲突。但是如果发生有不同的业务操作对同一版本的数据进行修 改,那么,先提交的操作(图中B)会把数据version更新为2,当A在B之后提交更新时发现数据的version已经被修改了,那么A的更新操作会失败。2.2、乐观锁定的第二种实现方式和第一种差不多同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳 (timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。使用举例:以MySQL InnoDB为例还是拿之前的实例来举:商品goods表中有一个字段status,status为1代表商品未被下单,status为2代表商品已经被下单,那么我们对某个商品下单时必须确保该商品status为1。假设商品的id为1。下单操作包括3步骤:1、查询出商品信息select (status,status,version) from t_goods where id=#{id}
2、根据商品信息生成订单3、修改商品status为2update t_goods
set status=2,version=version+1
where id=#{id} and version=#{version};
那么为了使用乐观锁,我们首先修改t_goods表,增加一个version字段,数据默认version值为1。t_goods表初始数据如下:mysql> select * from t_goods;
+----+--------+------+---------+
| id | status | name | version |
+----+--------+------+---------+
| 1 | 1 | 道具 | 1 |
| 2 | 2 | 装备 | 2 |
+----+--------+------+---------+
2 rows in set
mysql>
对于乐观锁的实现,我使用MyBatis来进行实践,具体如下:Goods实体类:public class Goods implements Serializable {
/**
* serialVersionUID:序列化ID.
*/
private static final long serialVersionUID = 6803791908148880587L;
/**
* id:主键id.
*/
private int id;
/**
* status:商品状态:1未下单、2已下单.
*/
private int status;
/**
* name:商品名称.
*/
private String name;
/**
* version:商品数据版本号.
*/
private int version;
@Override
public String toString(){
return "good id:"+id+",goods status:"+status+",goods name:"+name+",goods version:"+version;
}
//setter and getter
}
GoodsDao/**
* updateGoodsUseCAS:使用CAS(Compare and set)更新商品信息. <br/>
*
*/
int updateGoodsUseCAS(Goods goods);
mapper.xml
<update id="updateGoodsUseCAS" parameterType="Goods">
<![CDATA[
update t_goods
set status=#{status},name=#{name},version=version+1
where id=#{id} and version=#{version}
]]>
</update>
GoodsDaoTest测试类@Test
public void goodsDaoTest(){
int goodsId = 1;
//根据相同的id查询出商品信息,赋给2个对象
Goods goods1 = this.goodsDao.getGoodsById(goodsId);
Goods goods2 = this.goodsDao.getGoodsById(goodsId);
//打印当前商品信息
System.out.println(goods1);
System.out.println(goods2);
//更新商品信息1
goods1.setStatus(2);//修改status为2
int updateResult1 = this.goodsDao.updateGoodsUseCAS(goods1);
System.out.println("修改商品信息1"+(updateResult1==1?"成功":"失败"));
//更新商品信息2
goods1.setStatus(2);//修改status为2
int updateResult2 = this.goodsDao.updateGoodsUseCAS(goods1);
System.out.println("修改商品信息2"+(updateResult2==1?"成功":"失败"));
}
输出结果:good id:1,goods status:1,goods name:道具,goods version:1
good id:1,goods status:1,goods name:道具,goods version:1
修改商品信息1成功
修改商品信息2失败
说明:在GoodsDaoTest测试方法中,我们同时查出同一个版本的数据,赋给不同的goods对象,然后先修改good1对象然后执行更新操作,执行成功。然后我们修改goods2,执行更新操作时提示操作失败。此时t_goods表中数据如下:mysql> select * from t_goods;
+----+--------+------+---------+
| id | status | name | version |
+----+--------+------+---------+
| 1 | 2 | 道具 | 2 |
| 2 | 2 | 装备 | 2 |
+----+--------+------+---------+
2 rows in set
mysql>
我们可以看到 id为1的数据version已经在第一次更新时修改为2了。所以我们更新good2时update where条件已经不匹配了,所以更新不会成功,具体sql如下:update t_goods
set status=2,version=version+1
where id=#{id} and version=#{version};
这样我们就实现了乐观锁。
无事小神仙
MySQL 读写分离配置实践
一、环境准备master(虚拟机centos7,NAT模式,固定ip):192.168.131.129slave(win10,路由器局域网,DHCP协议):192.168.31.27由于MyCat是用Java写的,需要JDK1.7版本以上MySQL的root账户有远程访问权限1. 查看主从复制状态读写分离基于主从复制,查看主从复制状态 2. 查看JDK版本java -version
3. 打开root的远程连接权限一般MySQL Server和代理中间件是不在一台机器上的,涉及数据库的远程访问和连接我们可以拿root进行连接,也可以创建新的用户进行连接root用户默认是localhost,只能本地连接,不支持远程连接,所以需要root远程连接的权限打开。%表示允许任意地址连接,如果缩小权限,写成MyCat所在机器的ip地址就可以,用root连接MySQL服务器mysql> grant all privileges on *.* to 'root'@'%' identified by '123456' with grant option;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
mysql> quit
Bye
[root@localhost Downloads]# service mysqld restart # 若服务不存在,可尝试使用service mysql restart
Redirecting to /bin/systemctl restart mysqld.service
我们的MyCat和主库跑在同一台Linux上4. 安装MyCat安装lrzsz,用于windows和Linux传输文件(xftp也行)用rz命令将MyCat包传输到LinuxLinux上的文件上传到Windows:sz+文件路径解压MyCat包放到合适的目录下,可以放到/usr/local下mycat/bin:放的是可执行文件mycat/conf:放的MyCat的配置文件mycat/logs:放的MyCat的日志文件wrapper.log:记录启动过程中遇到的错误mycat.log:记录运行过程中遇到的错误由于我们是直接解压的,没有安装,为了不用手动指定mycat的路径,我们 在/usr/bin下建立软连接,连接用户目录下的mycat和我们解压路径下的mycat这样就不用指定路径,直接使用mycat二、配置文件配置文件在mycat/conf下1. server.xml用于配置client登录Mycat的账号密码,还可以配置白名单黑名单,限制客户的连接等用户配置不需要和MySQL的账号密码一样,因为我们的MySQL Client直接访问的是MyCat,再由MyCat登录MySQL Server,这里设置的用户、密码都是用来登录MyCat的USERDB是给客户端操作的逻辑库,由于MySQL Client访问的是MyCat,在MyCat上直接操作USERDB这个库即可,这个库其实是不存在的,这个库最终会映射到MySQL Server真实的MySQL库表上这个逻辑库看起来好像在MyCat一台机器上,实际上经过分库分表操作可能分配在不同的机器上,我们只需要操作这个逻辑库就可以,其他的不用关心。多个逻辑库的话,在标签schemas中间,用逗号分隔开即可防火墙配置2. schema.xmlschema.xml用于配置逻辑库和数据源、读写分离、分库分表信息等schema.xml配置以下三点:逻辑库和逻辑表:MySQL Client都是操作的MyCat上的逻辑库(schema)和逻辑表数据节点:这个库或者表的内容放在哪个节点(dataNode)上,这个节点对应具体的物理机器叫dataHost逻辑库、数据节点以及数据库主机名称都可以随便取,但以下地方需要保持相同maxCon、minCon:MyCat内置连接池的最大、最小连接量balance: 0:不开启读写分离 1:全部的readHost和stand by writeHost参与读操作的负载 ,比如2套1主2从,M1叫做writeHost,S1、S2、S3、S4 叫做readHost,M2叫做stand by writeHost 2:所有读操作随机在readHost和writeHost上分发(少用) 3:所有读请求随机分发到writeHost对应的readHost上执行(最常使用,所有的select操作都在slave上执行,master库只做写操作)writeType=0: 表示所有写操作发送到配置的第一个writeHost,第一个挂掉切换到还在的第二个 writeHostswitchType(切换的类型,当一个master挂了,切换到另一个master上): -1:不自动切换 1:根据心跳select user()自动切换 2:基于MySQL的主从同步状态决定是否进行切换,即MyCat发送show slave status给MySQL ServerwriteHost、readHost:配置写服务器(master)和读服务器(slave),readHost标签在writeHost内,表示读服务器是slave,图中黄色框中是配置了一个一主一从,嵌套多个readHost标签就是配置一主多从。图中并列的writeHost标签表示备份的写库,当master宕机后,slave也将无法和master配合工作,会切换到备份的写库继续工作。其实图中配置的是多主多从heartbeat:MyCat定时发送指定语句给MySQL Server,如果能正常返回数据,则表示正常工作;若不能正常返回数据,则表示机器故障,MyCat需要进行容灾切换如果slave有问题,master是正常,就会在master上做读和写操作如果master有问题,slave是正常,此时slave是没法单独使用的,它会在多主多从的配置中找下一套主从配置来使用如果主从都正常,master做写操作,slave做读操作三、启动服务查看配置文件mycat/conf/schema.xml启动MyCat服务查看端口这表示mycat正常监听8066和9066端口1. 配置文件问题一mycat/schema.xml中备份的主库没有结束标签配置好后,我们重启mycat程序查看mycat/logs/wrapper.log,记录了mycat启动过程中的错误2. 配置文件问题二mycat/schema.xml中读库的端口出错配置好后,我们重启mycat程序查看mycat/logs/wrapper.log,记录了mycat启动过程中的错误我们看到心跳不成功了,就应该判断是网络原因,或者是ip:port配置原因,于是我们看到了3309端口,就知道是配置的端口错误四、MyCat 9066端口和8066端口我们开启MyCat后台服务1. 9066管理端口在Linux Shell下登录MyCat的9066端口(使用mycat/conf/server.xml中配置的登录用户名和密码登录)登录MyCat后也是进入了一个MySQL Shell,monitor表示状态监控show @@help显示mycat支持的命令查看逻辑库:show @@database查看逻辑节点和真实库的映射关系show @@datanode查看数据源show @@datasourceM2是我们配置的备用的第二套写库,这种情况下,还要配置M2为M1的从,需要同步数据2. 8066数据端口在Linux Shell下登录mycat的8066端口(使用mycat/conf/server.xml中配置的登录用户名和密码登录)OpenCloundDB表示我们看到的是一个云状数据库,云后面是如何提供的库表的服务能力,我们是不知道的。mycat就是云DB,把后端所有的细节给客户端隐藏了,客户端只需要去处理代理服务器上的DB就可以了。可以看作一个反向代理服务器查看数据库这个逻辑库USERDB对应的就是真实库mytest五、验证读写分离查看查询日志general_log,这个日志记录了MySQL Server收到的所有SQL语句1. 打开查询日志general_log打开windows从库上的general_log在Linux下的MySQL Server中也打开一下查询日志2. 验证读操作在slave我们现在登录MyCat 8066数据端口,查询user表在Linux下的master服务器查看general_log,我们只看见了mycat发送的心跳包,并没有看见查询user表的SQL在windows下的slave服务器中查看general_log,看到了mycat发送的查询user表的SQL没有问题,现在读操作是正确发送给了slave3. 验证写操作在master我们现在登录MyCat 8066数据端口,给user表insert一条数据在Linux下的master服务器查看general_log,我们看见了insert数据的SQL在windows下的slave服务器中查看general_log,没有发现insert数据的SQL没有问题,写操作正确发送给了master4. 验证容灾功能我们在mycat/conf/schema.xml中配置的是多住多从,M1挂了,读写操作会全部转发到M2在我们当前环境中,就是Linux上的MySQL Server挂了,所有的读写操作都会转发给Windows上的MySQL Server关闭Linux的mysqld服务,相当于关闭了master我们现在登录MyCat 8066数据端口,对user表分别读写操作查看我们多主多从中备用系统的general_log,即Windows上的MySQL Server的general_log可以看见,由于master挂了,读写操作都被转发到了备用的Windows上的MySQL Server,证明容灾没有问题