SQL Server 高性能写入的一些总结,2024年最新大厂Mysql高频面试题

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图1 Users表设计

上面,我们定义了Users表,它包含账户名、密码、显示名称和注册日期等10个字段,其中,ID是一个自增的主键,user_resistered用来记录用户的注册时间,它设置了默认值GETDATE()。

接下来,我们将通过客户端代码实现数据存储到Users表中,具体的代码如下:

Creates a database connection.

var conn = new SqlConnection(ConfigurationManager.ConnectionStrings[“SQLCONN1”].ToString());

conn.Open();

This is a massive SQL injection vulnerability,

don’t ever write your own SQL statements with string formatting!

string sql = String.Format(

@"INSERT INTO jk_users (user_login, user_pass, user_nicename, user_email, user_status,display_name, user_url, user_activation_key)

VALUES (‘{0}’, ‘{1}’, ‘{2}’, ‘{3}’, ‘{4}’, ‘{5}’, ‘{6}’, ‘{7}’)",

userLogin, userPass, userNicename, userEmail, userStatus, displayName, userUrl, userActivationKey);

var cmd = new SqlCommand(sql, conn);

cmd.ExecuteNonQuery();

Because this call to Close() is not wrapped in a try/catch/finally clause,

it could be missed if an exception occurs above. Don’t do this!

conn.Close();

代码中的问题


上面,我们使用再普通不过的ADO.NET方式实现数据写入功能,但大家是否发现代码存在问题或可以改进的地方呢?

首先,我们在客户端代码中,创建一个数据库连接,它需要占用一定的系统资源,当操作完毕之后我们需要释放占用的系统资源,当然,我们可以手动释放资源,具体实现如下:

Creates a database connection.

var conn = new SqlConnection(ConfigurationManager.ConnectionStrings[“SQLCONN1”].ToString());

conn.Open();

This is a massive SQL injection vulnerability,

don’t ever write your own SQL statements with string formatting!

string sql = String.Format(

@"INSERT INTO jk_users (user_login, user_pass, user_nicename, user_email, user_status,display_name, user_url, user_activation_key)

VALUES (‘{0}’, ‘{1}’, ‘{2}’, ‘{3}’, ‘{4}’, ‘{5}’, ‘{6}’, ‘{7}’)",

userLogin, userPass, userNicename, userEmail, userStatus, displayName, userUrl, userActivationKey);

var cmd = new SqlCommand(sql, conn);

cmd.ExecuteNonQuery();

If throws an exception on cmd dispose.

cmd.Dispose();

conn can’t be disposed.

conn.Close();

conn.Dispose();

假如,在释放SqlCommand资源时抛出异常,那么在它后面的资源SqlConnection将得不到释放。我们仔细想想当发生异常时,可以通过try/catch捕获异常,所以无论是否发生异常都可以使用finally检查资源是否已经释放了,具体实现如下:

SqlCommand cmd = null;

SqlConnection conn = null;

try

{

Creates a database connection.

conn = new SqlConnection(ConfigurationManager.ConnectionStrings[“SQLCONN1”].ToString());

conn.Open();

This is a massive SQL injection vulnerability,

don’t ever write your own SQL statements with string formatting!

string sql = String.Format(

@"INSERT INTO jk_users (user_login, user_pass, user_nicename, user_email, user_status,display_name, user_url, user_activation_key)

VALUES (‘{0}’, ‘{1}’, ‘{2}’, ‘{3}’, ‘{4}’, ‘{5}’, ‘{6}’, ‘{7}’)",

userLogin, userPass, userNicename, userEmail, userStatus, displayName, userUrl, userActivationKey);

cmd = new SqlCommand(sql, conn);

cmd.ExecuteNonQuery();

}

finally

{

Regardless of whether there is an exception,

we will dispose the resource.

if (cmd != null) cmd.Dispose();

if (conn != null) conn.Dispose();

}

通过上面的finally方式处理了异常情况是很普遍的,但为了更安全释放资源,使得我们增加了finally和if语句,那么是否有更简洁的方法实现资源的安全释放呢?

其实,我们可以使用using语句实现资源的释放,具体实现如下:

using语句:定义一个范围,将在此范围之外释放一个或多个对象。

string sql = String.Format(

@"INSERT INTO jk_users (user_login, user_pass, user_nicename, user_email, user_status,display_name, user_url, user_activation_key)

VALUES (‘{0}’, ‘{1}’, ‘{2}’, ‘{3}’, ‘{4}’, ‘{5}’, ‘{6}’, ‘{7}’)",

userLogin, userPass, userNicename, userEmail, userStatus, displayName, userUrl, userActivationKey);

Creates a database connection.

using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings[“SQLCONN1”].ToString()))

using (var cmd = new SqlCommand(sql, conn))

{

Your code here.

}

上面的代码使用了using语句实现资源的释放,那么是否所有对象都可以使用using语句实现释放呢?

只有类型实现了IDisposable接口并且重写Dispose()方法可以使用using语句实现资源释放,由于SqlConnection和SqlCommand实现了IDisposable接口,那么我们可以使用using语句实现资源释放和异常处理。

在客户端代码中,我们使用拼接SQL语句方式实现数据写入,由于SQL语句是动态执行的,所以恶意用户可以通过拼接SQL的方式实施SQL注入攻击

对于SQL注入攻击,我们可以通过以下方式防御:

  • 正则表达校验用户输入

  • 参数化存储过程

  • 参数化SQL语句

  • 添加数据库新架构

  • LINQ to SQL

接下来,我们将通过参数化SQL语句防御SQL注入攻击,大家也可以使用其他的方法防御SQL注入攻击,具体实现代码如下:

Creates a database connection.

using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings[“SQLCONN1”].ToString()))

{

conn.Open();

string sql = string.Format(

@"INSERT INTO jk_users (user_login, user_pass, user_nicename, user_email,

user_status,display_name, user_url, user_activation_key)");

using (var cmd = new SqlCommand(sql, conn))

{

Parameterized SQL to defense injection attacks

cmd.Parameters.Add(“@user_login”, userLogin);

cmd.Parameters.Add(“@user_pass”, userPass);

cmd.Parameters.Add(“@user_nicename”, userNicename);

cmd.Parameters.Add(“@user_email”, userEmail);

cmd.Parameters.Add(“@user_status”, userStatus);

cmd.Parameters.Add(“@display_name”, displayName);

cmd.Parameters.Add(“@user_url”, userUrl);

cmd.Parameters.Add(“@user_activation_key”, userActivationKey);

cmd.ExecuteNonQuery();

}

}

上面通过参数化SQL语句和using语句对代码进行改进,现在代码的可读性更强了,而且也避免了SQL注入攻击和资源释放等问题。

接下来,让我们简单的测试一下代码执行时间,首先我们在代码中添加方法Stopwatch.StartNew()和Stopwatch.Stop()来计算写入代码的执行时间,具体代码如下:

calc insert 10000 records consume time.

var sw = Stopwatch.StartNew();

Creates a database connection.

using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings[“SQLCONN2”].ToString()))

{

conn.Open();

int cnt = 0;

while (cnt++ < 10000)

{

string sql = string.Format(@"INSERT INTO jk_users

(user_login, user_pass, user_nicename, user_email, user_status,display_name, user_url, user_activation_key)

VALUES (@user_login, @user_pass, @user_nicename, @user_email, @user_status, @display_name, @user_url, @user_activation_key)");

using (var cmd = new SqlCommand(sql, conn))

{

Parameterized SQL to defense injection attacks

cmd.Parameters.Add(“@user_login”, userLogin);

cmd.Parameters.Add(“@user_pass”, userPass);

cmd.Parameters.Add(“@user_nicename”, userNicename);

cmd.Parameters.Add(“@user_email”, userEmail);

cmd.Parameters.Add(“@user_status”, userStatus);

cmd.Parameters.Add(“@display_name”, displayName);

cmd.Parameters.Add(“@user_url”, userUrl);

cmd.Parameters.Add(“@user_activation_key”, userActivationKey);

cmd.ExecuteNonQuery();

}

}

}

sw.Stop();

}

上面,我们往数据库中写入了10000条数据,执行时间为 7.136秒(我的机器很破了),这样系统性能还是可以满足许多公司的需求了。

假如,用户请求量增大了,我们还能保证系统能满足需求吗?事实上,我们不应该满足于现有的系统性能,因为我们知道代码的执行效率还有很大的提升空间。

接下来,将进一步介绍代码改善的方法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图2 数据写入Users表

为了使数据库获得更快的写入速度,我们必须了解数据库在进行写入操作时的主要耗时。

数据库性能开销


连接时间

当我们执行conn.Open()时,首先,必须建立物理通道(例如套接字或命名管道),必须与服务器进行初次握手,必须分析连接字符串信息,必须由服务器对连接进行身份验证,必须运行检查以便在当前事务中登记,等等

这一系列操作可能需要一两秒钟时间,如果我们每次执行conn.Open()都有进行这一系列操作是很耗费时间的,为了使打开的连接成本最低,ADO.NET使用称为连接池的优化方法。

连接池:减少新连接需要打开的次数,只要用户在连接上调用 Open()方法,池进程就会检查池中是否有可用的连接,如果某个池连接可用,那么将该连接返回给调用者,而不是创建新连接;应用程序在该连接上调用 Close()Dispose() 时,池进程会将连接返回到活动连接池集中,而不是真正关闭连接,连接返回到池中之后,即可在下一个 Open 调用中重复使用。

解析器的开销

当我们向SQL Server传递SQL语句INSERT INTO …时,它需要对SQL语句进行解析,由于SQL Server解析器执行速度很快,所以解析时间往往是可以忽略不计,但我们仍然可以通过使用存储过程,而不是直SQL语句来减少解析器的开销。

数据库连接

为了提供ACID(事务的四个特性),SQL Server必须确保所有的数据库更改是有序的。它是通过使用锁来确保该数据库插入、删除或更新操作之间不会相互冲突(关于数据库的锁请参考这里)。

由于,大多数数据库都是面向多用户的环境,当我们对User表进行插入操作时,也许有成千上百的用户也在对User表进行操作,所以说,SQL Server必须确保这些操作是有序进行的。

那么,当SQL Server正在做所有这些事情时,它会产生锁,以确保用户获得有意义的结果。SQL Server保证每条语句执行时,数据库是完全可预测的(例如:预测SQL执行方式)和管理锁都需要耗费一定的时间。

约束处理

在插入数据时,每个约束(如:外键、默认值、SQL CHECK等)需要额外的时间来检测数据是否符合约束;由于SQL Server为了保证每个插入、更新或删除的记录都符合约束条件,所以,我们需要考虑是否应该在数据量大的表中增加约束条件。

Varchar

VARCHAR是数据库常用的类型,但它也可能导致意想不到的性能开销;每次我们存储可变长度的列,那么SQL Server必须做更多的内存管理;字符串可以很容易地消耗数百字节的内存的,如果我们在一个VARCHAR列中设置索引,那么SQL Server执行B-树搜索时,就需要进行O(字符串长度)次比较,然而,整数字段比较次数只受限于内存延迟和CPU频率。

磁盘IO

SQL Server最终会将数据写入到磁盘中,首先,SQL Server把数据写入到事务日志中,当执行备份时,事务日志会合并到永久的数据库文件中;这一系列操作由后台完成,它不会影响到数据查询的速度,但每个事物都必须拥有属于自己的磁盘空间,所以我们可以通过给事务日志和主数据文件分配独立的磁盘空间减少IO开销,当然,最好解决办法是尽可能减少事务的数量。

正如大家所看到的,我们通过优化联接时间、 解析器的开销、 数据库联接、约束处理,、Varchar和磁盘IO等方法来优化数据库,接下来,我们将对前面的例子进行进一步的优化。

使用存储过程


前面例子中,我们把SQL代码直接Hardcode在客户端代码中,那么,数据库就需要使用解析器解析客户端中SQL语句,所以我们可以改用使用存储过程,从而,减少解析器的时间开销;更重要的一点是,由于SQL是动态执行的,所以我们修改存储过程中的SQL语句也无需重新编译和发布程序。

User表中的字段user_registered设置了默认值(GETDATE()),那么我们通过消除表默认值约束来提高系统的性能,简而言之,我们需要提供字段user_registered的值。

接下来,让我们省去User表中的默认值约束和增加存储过程,具体代码如下:

– =============================================

– Author: JKhuang

– Create date: 08/16/2012

– Description: Creates stored procedure to insert

– data into table jk_users.

– =============================================

ALTER PROCEDURE [dbo].[SP_Insert_jk_users]

@user_login varchar(60),

@user_pass varchar(64),

@user_nicename varchar(50),

@user_email varchar(100),

@user_url varchar(100),

@user_activation_key varchar(60),

@user_status int,

@display_name varchar(250)

AS

BEGIN

SET NOCOUNT ON;

– The stored procedure allows SQL server to avoid virtually all parser work

INSERT INTO jk_users

(user_login, user_pass, user_nicename, user_email, user_status,display_name, user_url, user_activation_key, user_registered)

VALUES (@user_login, @user_pass, @user_nicename, @user_email, @user_status, @display_name, @user_url, @user_activation_key, GETDATE());

END

上面我们定义了存储过程SP_Insert_jk_users向表中插入数据,当我们重新执行代码时,发现数据插入的时间缩短为6.7401秒。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图3数据写入时间

使用数据库事务


想想数据是否可以延长写入到数据库中,是否可以批量地写入呢?如果允许延迟一段时间才写入到数据库中,那么我们可以使用Transaction来延迟数据写入。

数据库事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。 SQL Server确保事务执行成功后,数据写入到数据库中,反之,事务将回滚。

如果我们对数据库进行十次独立的操作,那么SQL Server就需要分配十次锁开销,但如果把这些操作都封装在一个事务中,那么SQL Server只需要分配一次锁开销。

calc insert 10000 records consume time.

var sw = Stopwatch.StartNew();

Creates a database connection.

using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings[“SQLCONN2”].ToString()))

{

conn.Open();

int cnt = 0;

SqlTransaction trans = conn.BeginTransaction();

while (cnt++ < 10000)

{

using (var cmd = new SqlCommand(“SP_Insert_jk_users”, conn))

{

Parameterized SQL to defense injection attacks

cmd.CommandType = CommandType.StoredProcedure;

Uses transcation to batch insert data.

To avoid lock and connection overhead.

cmd.Transaction = trans;

cmd.Parameters.Add(“@user_login”, userLogin);

cmd.Parameters.Add(“@user_pass”, userPass);

cmd.Parameters.Add(“@user_nicename”, userNicename);

cmd.Parameters.Add(“@user_email”, userEmail);

cmd.Parameters.Add(“@user_status”, userStatus);

cmd.Parameters.Add(“@display_name”, displayName);

cmd.Parameters.Add(“@user_url”, userUrl);

cmd.Parameters.Add(“@user_activation_key”, userActivationKey);

cmd.ExecuteNonQuery();

}

}

If no exception, commit transcation.

trans.Commit();

}

sw.Stop();

}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后

我还通过一些渠道整理了一些大厂真实面试主要有:蚂蚁金服、拼多多、阿里云、百度、唯品会、携程、丰巢科技、乐信、软通动力、OPPO、银盛支付、中国平安等初,中级,高级Java面试题集合,附带超详细答案,希望能帮助到大家。

新鲜出炉的蚂蚁金服面经,熬夜整理出来的答案,已有千人收藏

还有专门针对JVM、SPringBoot、SpringCloud、数据库、Linux、缓存、消息中间件、源码等相关面试题。

新鲜出炉的蚂蚁金服面经,熬夜整理出来的答案,已有千人收藏

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

sw.Stop();

}

[外链图片转存中…(img-mEHJSTBE-1713655142368)]

最后

我还通过一些渠道整理了一些大厂真实面试主要有:蚂蚁金服、拼多多、阿里云、百度、唯品会、携程、丰巢科技、乐信、软通动力、OPPO、银盛支付、中国平安等初,中级,高级Java面试题集合,附带超详细答案,希望能帮助到大家。

[外链图片转存中…(img-vRS7jF7t-1713655142369)]

还有专门针对JVM、SPringBoot、SpringCloud、数据库、Linux、缓存、消息中间件、源码等相关面试题。

[外链图片转存中…(img-2FZAxswe-1713655142369)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-EhPKRgTY-1713655142370)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值