Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <blockquote> <p>My question is, is it a problem to have hundreds of thousands of tables in your SQL Server?</p> </blockquote> <p>Yes. It is a <strong>huge</strong> problem to have this many tables in your SQL Server. Every object has to be tracked by SQL Server as metadata, and once you include indexes, referential constraints, primary keys, defaults, and so on, then you are talking about <em>millions</em> of database objects.</p> <p>While SQL Server may theoretically be able to handle 2<sup>32</sup> objects, rest assured that it will start buckling under the load much sooner than that.</p> <p>And if the database doesn't collapse, your developers and IT staff almost certainly will. I get nervous when I see more than a thousand tables or so; show me a database with <em>hundreds of thousands</em> and I will run away screaming.</p> <p>Creating hundreds of thousands of tables as a poor-man's partitioning strategy will eliminate your ability to do any of the following:</p> <ul> <li>Write efficient queries (how do you <code>SELECT</code> multiple categories?)</li> <li>Maintain unique identities (as you've already discovered)</li> <li>Maintain referential integrity (unless you like managing 300,000 foreign keys)</li> <li>Perform ranged updates</li> <li>Write clean application code</li> <li>Maintain any sort of history</li> <li>Enforce proper security (it seems evident that users would have to be able to initiate these create/drops - very dangerous)</li> <li>Cache properly - 100,000 tables means 100,000 different execution plans all competing for the same memory, which you likely don't have enough of;</li> <li>Hire a DBA (because rest assured, they will quit as soon as they see your database).</li> </ul> <p>On the other hand, it's <strong>not</strong> a problem at all to have hundreds of thousands of <em>rows</em>, or even <em>millions</em> of rows, in a single <em>table</em> - that's the way SQL Server and other SQL RDBMSes were designed to be used and they are very well-optimized for this case.</p> <blockquote> <p>The drop in O(1) is extremely desirable to me. Maybe there's a completely different solution I'm not thinking of?</p> </blockquote> <p>The typical solution to performance problems in databases is, in order of preference:</p> <ul> <li>Run a profiler to determine what the slowest parts of the query are;</li> <li>Improve the query, if possible (i.e. by eliminating non-sargable predicates);</li> <li>Normalize or add indexes to eliminate those bottlenecks;</li> <li>Denormalize when necessary (not generally applicable to deletes);</li> <li>If cascade constraints or triggers are involved, disable those for the duration of the transaction and blow out the cascades manually.</li> </ul> <p>But the reality here is that you <em>don't <strong>need</strong> a "solution."</em></p> <p>"Millions and millions of rows" is not a lot in a SQL Server database. It is <strong>very quick</strong> to delete a few thousand rows from a table of millions by simply indexing on the column you wish to delete from - in this case <code>CategoryID</code>. SQL Server can do this without breaking a sweat.</p> <p>In fact, deletions normally have an O(M log N) complexity (N = number of rows, M = number of rows to delete). In order to achieve an O(1) deletion time, you'd be sacrificing almost every benefit that SQL Server provides in the first place.</p> <p>O(M log N) may not be as fast as O(1), but the kind of slowdowns you're talking about (several minutes to delete) <em>must</em> have a secondary cause. The numbers do not add up, and to demonstrate this, I've gone ahead and produced a benchmark:</p> <hr> <h2>Table Schema:</h2> <pre><code>CREATE TABLE Stars ( StarID int NOT NULL IDENTITY(1, 1) CONSTRAINT PK_Stars PRIMARY KEY CLUSTERED, CategoryID smallint NOT NULL, StarName varchar(200) ) CREATE INDEX IX_Stars_Category ON Stars (CategoryID) </code></pre> <p>Note that this schema is not even really optimized for <code>DELETE</code> operations, it's a fairly run-of-the-mill table schema you might see in SQL server. If this table has no relationships, then we don't need the surrogate key or clustered index (or we could put the clustered index on the category). I'll come back to that later.</p> <h2>Sample Data:</h2> <p>This will populate the table with 10 million rows, using 500 categories (i.e. a cardinality of 1:20,000 per category). You can tweak the parameters to change the amount of data and/or cardinality.</p> <pre><code>SET NOCOUNT ON DECLARE @BatchSize int, @BatchNum int, @BatchCount int, @StatusMsg nvarchar(100) SET @BatchSize = 1000 SET @BatchCount = 10000 SET @BatchNum = 1 WHILE (@BatchNum &lt;= @BatchCount) BEGIN SET @StatusMsg = N'Inserting rows - batch #' + CAST(@BatchNum AS nvarchar(5)) RAISERROR(@StatusMsg, 0, 1) WITH NOWAIT INSERT Stars2 (CategoryID, StarName) SELECT v.number % 500, CAST(RAND() * v.number AS varchar(200)) FROM master.dbo.spt_values v WHERE v.type = 'P' AND v.number &gt;= 1 AND v.number &lt;= @BatchSize SET @BatchNum = @BatchNum + 1 END </code></pre> <h2>Profile Script</h2> <p>The simplest of them all...</p> <pre><code>DELETE FROM Stars WHERE CategoryID = 50 </code></pre> <h2>Results:</h2> <p>This was tested on an <strong>5-year old workstation machine</strong> running, IIRC, a 32-bit dual-core AMD Athlon and a cheap 7200 RPM SATA drive.</p> <p>I ran the test 10 times using different CategoryIDs. The slowest time (cold cache) was about 5 seconds. The fastest time was 1 second.</p> <p>Perhaps not as fast as simply dropping the table, but nowhere near the multi-minute deletion times you mentioned. And remember, this isn't even on a decent machine!</p> <h2>But we can do better...</h2> <p>Everything about your question implies that this data <em>isn't related</em>. If you don't have relations, you don't need the surrogate key, and can get rid of one of the indexes, moving the clustered index to the <code>CategoryID</code> column.</p> <p>Now, as a rule, clustered indexes on non-unique/non-sequential columns are not a good practice. But we're just benchmarking here, so we'll do it anyway:</p> <pre><code>CREATE TABLE Stars ( CategoryID smallint NOT NULL, StarName varchar(200) ) CREATE CLUSTERED INDEX IX_Stars_Category ON Stars (CategoryID) </code></pre> <p>Run the same test data generator on this (incurring a mind-boggling number of page splits) and the same deletion took an average of just <strong>62 milliseconds</strong>, and 190 from a cold cache (outlier). And for reference, if the index is made nonclustered (no clustered index at all) then the delete time only goes up to an average of 606 ms.</p> <h2>Conclusion:</h2> <p>If you're seeing delete times of <em>several minutes</em> - or even <em>several seconds</em> then something is <strong>very, very wrong</strong>.</p> <p>Possible factors are:</p> <ul> <li><p>Statistics aren't up to date (shouldn't be an issue here, but if it is, just run <code>sp_updatestats</code>);</p></li> <li><p>Lack of indexing (although, curiously, removing the <code>IX_Stars_Category</code> index in the first example actually leads to a <em>faster</em> overall delete, because the clustered index scan is faster than the nonclustered index delete);</p></li> <li><p>Improperly-chosen data types. If you only have <em>millions</em> of rows, as opposed to <em>billions</em>, then you do not need a <code>bigint</code> on the <code>StarID</code>. You <strong>definitely</strong> don't need it on the <code>CategoryID</code> - if you have fewer than 32,768 categories then you can even do with a <code>smallint</code>. Every byte of unnecessary data in each row adds an I/O cost.</p></li> <li><p>Lock contention. Maybe the problem isn't actually delete speed at all; maybe some other script or process is holding locks on <code>Star</code> rows and the <code>DELETE</code> just sits around waiting for them to let go.</p></li> <li><p><em>Extremely</em> poor hardware. I was able to run this without any problems on a pretty lousy machine, but if you're running this database on a '90s-era Presario or some similar machine that's preposterously unsuitable for hosting an instance of SQL Server, and it's heavily-loaded, then you're obviously going to run into problems.</p></li> <li><p>Very expensive foreign keys, triggers, constraints, or other database objects which you haven't included in your example, which might be adding a high cost. Your execution plan should clearly show this (in the optimized example above, it's just a single Clustered Index Delete).</p></li> </ul> <p>I honestly cannot think of any other possibilities. Deletes in SQL Server just <em>aren't that slow</em>.</p> <hr> <p>If you're able to run these benchmarks and see roughly the same performance I saw (or better), then it means the problem is with your database design and optimization strategy, not with SQL Server or the asymptotic complexity of deletions. I would suggest, as a starting point, to read a little about optimization:</p> <ul> <li><a href="http://www.databasejournal.com/features/mssql/article.php/1576231/SQL-Server-Optimization-Tips-for-Designing-Tables.htm" rel="noreferrer">SQL Server Optimization Tips</a> (Database Journal)</li> <li><a href="http://msdn.microsoft.com/en-us/library/aa964133(SQL.90).aspx" rel="noreferrer">SQL Server Optimization</a> (MSDN)</li> <li><a href="http://msdn.microsoft.com/en-us/library/ms998577.aspx" rel="noreferrer">Improving SQL Server Performance</a> (MSDN)</li> <li><a href="http://blogs.msdn.com/sqlqueryprocessing/default.aspx" rel="noreferrer">SQL Server Query Processing Team Blog</a></li> <li><a href="http://www.sql-server-performance.com/" rel="noreferrer">SQL Server Performance</a> (particularly their tips on <a href="http://www.sql-server-performance.com/tips/optimizing_indexes_general_p1.aspx" rel="noreferrer">indexes</a>)</li> </ul> <p>If this <em>still</em> doesn't help you, then I can offer the following additional suggestions:</p> <ul> <li><p>Upgrade to SQL Server 2008, which gives you a myriad of <a href="http://msdn.microsoft.com/en-us/library/dd894051.aspx" rel="noreferrer">compression options</a> that can vastly improve I/O performance;</p></li> <li><p>Consider pre-compressing the per-category <code>Star</code> data into a compact serialized list (using the <code>BinaryWriter</code> class in .NET), and store it in a <code>varbinary</code> column. This way you can have one row per category. This violates 1NF rules, but since you don't seem to be doing anything with individual <code>Star</code> data from within the database anyway anyway, I doubt you'd be losing much.</p></li> <li><p>Consider using a non-relational database or storage format, such as <a href="http://www.db4o.com/" rel="noreferrer">db4o</a> or <a href="http://cassandra.apache.org/" rel="noreferrer">Cassandra</a>. Instead of implementing a known database anti-pattern (the infamous "data dump"), use a tool that is actually designed for that kind of storage and access pattern.</p></li> </ul>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload